/*
 * Copyright 2019 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.realm.processor

import com.squareup.javawriter.JavaWriter
import io.realm.processor.Utils.fieldTypeHasPrimaryKey
import io.realm.processor.Utils.getGenericType
import io.realm.processor.ext.beginMethod
import io.realm.processor.ext.beginType
import java.io.BufferedWriter
import java.io.IOException
import java.util.*
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.Modifier
import javax.lang.model.element.VariableElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeMirror
import javax.tools.JavaFileObject

/**
 * This class is responsible for generating the Realm Proxy classes for each model class defined
 * by the user. This is the main entrypoint for users interacting with Realm, but it is hidden
 * from them as an implementation detail generated by the annotation processor.
 *
 * See [RealmProcessor] for a more detailed description on what files Realm creates internally and
 * why.
 *
 * NOTE: This file will look strangely formatted to you. This is on purpose. The intent of the
 * formatting it is to better represent the outputted code, not make _this_ code as readable as
 * possible. This mean two things:
 *
 * 1. Attempt to keep code that emit a single line to one line here.
 * 2. Attempt to indent the emit functions that would emulate the blocks created by the generated code.
 */
class RealmProxyClassGenerator(private val processingEnvironment: ProcessingEnvironment,
                               private val typeMirrors: TypeMirrors,
                               private val metadata: ClassMetaData,
                               private val classCollection: ClassCollection) {

    private val simpleJavaClassName: SimpleClassName = metadata.simpleJavaClassName
    private val qualifiedJavaClassName: QualifiedClassName = metadata.qualifiedClassName
    private val internalClassName: String = metadata.internalClassName
    private val interfaceName: SimpleClassName = Utils.getProxyInterfaceName(qualifiedJavaClassName)
    private val generatedClassName: QualifiedClassName = QualifiedClassName(String.format(Locale.US, "%s.%s", Constants.REALM_PACKAGE_NAME, Utils.getProxyClassName(qualifiedJavaClassName)))
    // See the configuration for the Android debug build type,
    //  in the realm-library project, for an example of how to set this flag.
    private val suppressWarnings: Boolean = !"false".equals(processingEnvironment.options[OPTION_SUPPRESS_WARNINGS], ignoreCase = true)

    lateinit var sourceFile: JavaFileObject
    @Throws(IOException::class, UnsupportedOperationException::class)
    fun generate() {
        sourceFile = processingEnvironment.filer.createSourceFile(generatedClassName.toString())

        val imports = ArrayList(IMPORTS)
        if (metadata.backlinkFields.isNotEmpty()) {
            imports.add("io.realm.internal.UncheckedRow")
        }

        val writer = JavaWriter(BufferedWriter(sourceFile.openWriter()))
        writer.apply {
            indent = Constants.INDENT // Set source code indent
            emitPackage(Constants.REALM_PACKAGE_NAME)
            emitEmptyLine()
            emitImports(imports)
            emitEmptyLine()

            // Begin the class definition
            if (suppressWarnings) {
                emitAnnotation("SuppressWarnings(\"all\")")
            }
            beginType(generatedClassName, "class", setOf(Modifier.PUBLIC), qualifiedJavaClassName, arrayOf("RealmObjectProxy", interfaceName.toString()))
            emitEmptyLine()

            // Emit class content
            emitColumnInfoClass(writer)
            emitClassFields(writer)
            emitInstanceFields(writer)
            emitConstructor(writer)
            emitInjectContextMethod(writer)
            emitPersistedFieldAccessors(writer)
            emitBacklinkFieldAccessors(writer)
            emitCreateExpectedObjectSchemaInfo(writer)
            emitGetExpectedObjectSchemaInfo(writer)
            emitCreateColumnInfoMethod(writer)
            emitGetSimpleClassNameMethod(writer)
            emitCreateOrUpdateUsingJsonObject(writer)
            emitCreateUsingJsonStream(writer)
            emitNewProxyInstance(writer)
            emitCopyOrUpdateMethod(writer)
            emitCopyMethod(writer)
            emitInsertMethod(writer)
            emitInsertListMethod(writer)
            emitInsertOrUpdateMethod(writer)
            emitInsertOrUpdateListMethod(writer)
            emitCreateDetachedCopyMethod(writer)
            emitUpdateMethod(writer)
            emitUpdateEmbeddedObjectMethod(writer)
            emitToStringMethod(writer)
            emitRealmObjectProxyImplementation(writer)
            emitHashcodeMethod(writer)
            emitEqualsMethod(writer)

            // End the class definition
            endType()
            close()
        }
    }

    @Throws(IOException::class)
    private fun emitColumnInfoClass(writer: JavaWriter) {
        writer.apply {
            beginType(columnInfoClassName(), "class", EnumSet.of(Modifier.STATIC, Modifier.FINAL), "ColumnInfo")                               // base class

            // fields
            for (variableElement in metadata.fields) {
                emitField("long", columnKeyVarName(variableElement))
            }
            emitEmptyLine()

            // constructor #1
            beginConstructor(EnumSet.noneOf(Modifier::class.java), "OsSchemaInfo", "schemaInfo")
                emitStatement("super(%s)", metadata.fields.size)
                emitStatement("OsObjectSchemaInfo objectSchemaInfo = schemaInfo.getObjectSchemaInfo(\"%1\$s\")", internalClassName)
                for (field in metadata.fields) {
                    emitStatement("this.%1\$sColKey = addColumnDetails(\"%1\$s\", \"%2\$s\", objectSchemaInfo)", field.javaName, field.internalFieldName)
                }
                for (backlink in metadata.backlinkFields) {
                    val sourceClass = classCollection.getClassFromQualifiedName(backlink.sourceClass!!)
                    val internalSourceClassName = sourceClass.internalClassName
                    val internalSourceFieldName = sourceClass.getInternalFieldName(backlink.sourceField!!)
                    emitStatement("addBacklinkDetails(schemaInfo, \"%s\", \"%s\", \"%s\")", backlink.targetField, internalSourceClassName, internalSourceFieldName)
                }
            endConstructor()
            emitEmptyLine()

            // constructor #2
            beginConstructor(EnumSet.noneOf(Modifier::class.java),"ColumnInfo", "src", "boolean", "mutable")
                emitStatement("super(src, mutable)")
                emitStatement("copy(src, this)")
            endConstructor()
            emitEmptyLine()

            // no-args copy method
            emitAnnotation("Override")
            beginMethod("ColumnInfo", "copy", EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), "boolean", "mutable")
                emitStatement("return new %s(this, mutable)", columnInfoClassName())
            endMethod()
            emitEmptyLine()

            // copy method
            emitAnnotation("Override")
            beginMethod("void", "copy", EnumSet.of(Modifier.PROTECTED, Modifier.FINAL), "ColumnInfo", "rawSrc", "ColumnInfo", "rawDst")
                emitStatement("final %1\$s src = (%1\$s) rawSrc", columnInfoClassName())
                emitStatement("final %1\$s dst = (%1\$s) rawDst", columnInfoClassName())
                for (variableElement in metadata.fields) {
                    emitStatement("dst.%1\$s = src.%1\$s", columnKeyVarName(variableElement))
                }
            endMethod()
            endType()
        }
    }

    @Throws(IOException::class)
    private fun emitClassFields(writer: JavaWriter) {
        writer.apply {
            emitEmptyLine()
            // This should ideally have been placed outside the Proxy classes, but due to an unknown
            // issue in the compile-testing framework, this kept failing tests. Keeping it here
            // fixes that.
            emitField("String", "NO_ALIAS", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL), "\"\"")
            emitField("OsObjectSchemaInfo", "expectedObjectSchemaInfo", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL),"createExpectedObjectSchemaInfo()")
        }
    }

    @Throws(IOException::class)
    private fun emitInstanceFields(writer: JavaWriter) {
        writer.apply {
            emitEmptyLine()
            emitField(columnInfoClassName(), "columnInfo", EnumSet.of(Modifier.PRIVATE))
            emitField("ProxyState<$qualifiedJavaClassName>", "proxyState", EnumSet.of(Modifier.PRIVATE))

            for (variableElement in metadata.fields) {
                if (Utils.isMutableRealmInteger(variableElement)) {
                    emitMutableRealmIntegerField(writer, variableElement)
                } else if (Utils.isRealmList(variableElement)) {
                    val genericType = Utils.getGenericTypeQualifiedName(variableElement)
                    emitField("RealmList<$genericType>", "${variableElement.simpleName}RealmList", EnumSet.of(Modifier.PRIVATE))
                } else if (Utils.isRealmDictionary(variableElement)) {
                    val valueType = Utils.getDictionaryValueTypeQualifiedName(variableElement)
                    emitField("RealmDictionary<$valueType>", "${variableElement.simpleName}RealmDictionary", EnumSet.of(Modifier.PRIVATE))
                } else if (Utils.isRealmSet(variableElement)) {
                    val valueType = Utils.getGenericTypeQualifiedName(variableElement)
                    emitField("RealmSet<$valueType>", "${variableElement.simpleName}RealmSet", EnumSet.of(Modifier.PRIVATE))
                }
            }

            for (backlink in metadata.backlinkFields) {
                emitField(backlink.targetFieldType, backlink.targetField + BACKLINKS_FIELD_EXTENSION, EnumSet.of(Modifier.PRIVATE))
            }
        }
    }

    // The anonymous subclass of MutableRealmInteger.Managed holds a reference to this proxy.
    // Even if all other references to the proxy are dropped, the proxy will not be GCed until
    // the MutableInteger that it owns, also becomes unreachable.
    @Throws(IOException::class)
    private fun emitMutableRealmIntegerField(writer: JavaWriter, variableElement: VariableElement) {
        writer.apply {
            emitField("MutableRealmInteger.Managed",
                    mutableRealmIntegerFieldName(variableElement),
                    EnumSet.of(Modifier.PRIVATE, Modifier.FINAL),
                    String.format(
                            "new MutableRealmInteger.Managed<%1\$s>() {\n"
                                    + "    @Override protected ProxyState<%1\$s> getProxyState() { return proxyState; }\n"
                                    + "    @Override protected long getColumnIndex() { return columnInfo.%2\$s; }\n"
                                    + "}",
                            qualifiedJavaClassName, columnKeyVarName(variableElement)))
        }
    }

    @Throws(IOException::class)
    private fun emitConstructor(writer: JavaWriter) {
        writer.apply {
            emitEmptyLine()
            beginConstructor(EnumSet.noneOf(Modifier::class.java))
                emitStatement("proxyState.setConstructionFinished()")
            endConstructor()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitPersistedFieldAccessors(writer: JavaWriter) {
        for (field in metadata.fields) {
            val fieldName = field.simpleName.toString()
            val fieldTypeCanonicalName = field.asType().toString()
            when {
                Constants.JAVA_TO_REALM_TYPES.containsKey(fieldTypeCanonicalName) -> emitPrimitiveType(writer, field, fieldName, fieldTypeCanonicalName)
                Utils.isMutableRealmInteger(field) -> emitMutableRealmInteger(writer, field, fieldName, fieldTypeCanonicalName)
                Utils.isRealmAny(field) -> emitRealmAny(writer, field, fieldName, fieldTypeCanonicalName)
                Utils.isRealmModel(field) -> emitRealmModel(writer, field, fieldName, fieldTypeCanonicalName)
                Utils.isRealmList(field) -> {
                    val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field)
                    emitRealmList(writer, field, fieldName, fieldTypeCanonicalName, elementTypeMirror)
                }
                Utils.isRealmDictionary(field) -> {
                    val valueTypeMirror = TypeMirrors.getRealmDictionaryElementTypeMirror(field)
                    emitRealmDictionary(writer, field, fieldName, fieldTypeCanonicalName, requireNotNull(valueTypeMirror))
                }
                Utils.isRealmSet(field) -> {
                    val valueTypeMirror = TypeMirrors.getRealmSetElementTypeMirror(field)
                    emitRealmSet(writer, field, fieldName, fieldTypeCanonicalName, requireNotNull(valueTypeMirror))
                }
                else -> throw UnsupportedOperationException(String.format(Locale.US, "Field \"%s\" of type \"%s\" is not supported.", fieldName, fieldTypeCanonicalName))
            }
            writer.emitEmptyLine()
        }
    }

    /**
     * Emit Set/Get methods for Primitives and boxed types
     */
    @Throws(IOException::class)
    private fun emitPrimitiveType(
            writer: JavaWriter,
            field: VariableElement,
            fieldName: String,
            fieldTypeCanonicalName: String) {

        val fieldJavaType: String? = getRealmTypeChecked(field).javaType

        writer.apply {
            // Getter - Start
            emitAnnotation("Override")
            emitAnnotation("SuppressWarnings", "\"cast\"")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")

                // For String and bytes[], null value will be returned by JNI code. Try to save one JNI call here.
                if (metadata.isNullable(field) && !Utils.isString(field) && !Utils.isByteArray(field)) {
                    beginControlFlow("if (proxyState.getRow\$realm().isNull(%s))", fieldColKeyVariableReference(field))
                        emitStatement("return null")
                    endControlFlow()
                }

                // For Boxed types, this should be the corresponding primitive types. Others remain the same.
                val castingBackType: String = if (Utils.isBoxedType(fieldTypeCanonicalName)) {
                    val typeUtils = processingEnvironment.typeUtils
                    typeUtils.unboxedType(field.asType()).toString()
                } else {
                    fieldTypeCanonicalName
                }

                emitStatement("return (%s) proxyState.getRow\$realm().get%s(%s)", castingBackType, fieldJavaType, fieldColKeyVariableReference(field))
            endMethod()
            emitEmptyLine()
            // Getter - End

            // Setter - Start
            emitAnnotation("Override")
            beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value")
                emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) {
                    // set value as default value
                    emitStatement("final Row row = proxyState.getRow\$realm()")
                    if (metadata.isNullable(field)) {
                        beginControlFlow("if (value == null)")
                            emitStatement("row.getTable().setNull(%s, row.getObjectKey(), true)", fieldColKeyVariableReference(field))
                            emitStatement("return")
                        endControlFlow()
                    } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) {
                        beginControlFlow("if (value == null)")
                            emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName)
                        endControlFlow()
                    }
                    emitStatement("row.getTable().set%s(%s, row.getObjectKey(), value, true)", fieldJavaType, fieldColKeyVariableReference(field))
                    emitStatement("return")
                }
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                // Although setting null value for String and bytes[] can be handled by the JNI code, we still generate the same code here.
                // Compared with getter, null value won't trigger more native calls in setter which is relatively cheaper.
                if (metadata.isPrimaryKey(field)) {
                    // Primary key is not allowed to be changed after object created.
                    emitStatement(Constants.STATEMENT_EXCEPTION_PRIMARY_KEY_CANNOT_BE_CHANGED, fieldName)
                } else {
                    if (metadata.isNullable(field)) {
                        beginControlFlow("if (value == null)")
                            emitStatement("proxyState.getRow\$realm().setNull(%s)", fieldColKeyVariableReference(field))
                            emitStatement("return")
                        endControlFlow()
                    } else if (!metadata.isNullable(field) && !Utils.isPrimitiveType(field)) {
                        // Same reason, throw IAE earlier.
                        beginControlFlow("if (value == null)")
                            emitStatement(Constants.STATEMENT_EXCEPTION_ILLEGAL_NULL_VALUE, fieldName)
                        endControlFlow()
                    }
                    emitStatement("proxyState.getRow\$realm().set%s(%s, value)", fieldJavaType, fieldColKeyVariableReference(field))
                }
            endMethod()
            // Setter - End
        }
    }

    /**
     * Emit Get method for mutable Realm Integer fields.
     */
    @Throws(IOException::class)
    private fun emitMutableRealmInteger(writer: JavaWriter, field: VariableElement, fieldName: String, fieldTypeCanonicalName: String) {
        writer.apply {
            emitAnnotation("Override")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
            emitStatement("proxyState.getRealm\$realm().checkIfValid()")
            emitStatement("return this.%s", mutableRealmIntegerFieldName(field))
            endMethod()
        }
    }

    /**
     * Emit Get method for RealmAny fields.
     */
    @Throws(IOException::class)
    private fun emitRealmAny(writer: JavaWriter, field: VariableElement, fieldName: String, fieldTypeCanonicalName: String) {
        writer.apply {
            // Getter - Start
            emitAnnotation("Override")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                emitStatement("NativeRealmAny nativeRealmAny = proxyState.getRow\$realm().getNativeRealmAny(%s)", fieldColKeyVariableReference(field))
                emitStatement("return new RealmAny(RealmAnyOperator.fromNativeRealmAny(proxyState.getRealm\$realm(), nativeRealmAny))")
            endMethod()
            // Getter - End

            emitEmptyLine()

            // Setter - Start
            emitAnnotation("Override")
            beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value")

            emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) {
                beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString())
                    emitStatement("return")
                endControlFlow()
                emitEmptyLine()
                emitStatement("value = ProxyUtils.copyToRealmIfNeeded(proxyState, value)")
                emitEmptyLine()
                emitStatement("final Row row = proxyState.getRow\$realm()")
                beginControlFlow("if (value == null)")
                    emitStatement("row.getTable().setNull(%s, row.getObjectKey(), true)", fieldColKeyVariableReference(field))
                    emitStatement("return")
                endControlFlow()
                emitStatement("row.getTable().setRealmAny(%s, row.getObjectKey(), value.getNativePtr(), true)", fieldColKeyVariableReference(field))
                emitStatement("return")

            }
            emitEmptyLine()
            emitStatement("proxyState.getRealm\$realm().checkIfValid()")
            emitEmptyLine()
            beginControlFlow("if (value == null)")
                emitStatement("proxyState.getRow\$realm().setNull(%s)", fieldColKeyVariableReference(field))
                emitStatement("return")
            endControlFlow()
            emitStatement("value = ProxyUtils.copyToRealmIfNeeded(proxyState, value)")
            emitStatement("proxyState.getRow\$realm().setRealmAny(%s, value.getNativePtr())", fieldColKeyVariableReference(field))
            endMethod()
            // Setter - End
        }
    }

    /**
     * Emit Set/Get methods for RealmModel fields.
     */
    @Throws(IOException::class)
    private fun emitRealmModel(writer: JavaWriter,
            field: VariableElement,
            fieldName: String,
            fieldTypeCanonicalName: String) {
        writer.apply {
            // Getter - Start
            emitAnnotation("Override")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                beginControlFlow("if (proxyState.getRow\$realm().isNullLink(%s))", fieldColKeyVariableReference(field))
                    emitStatement("return null")
                endControlFlow()
                emitStatement("return proxyState.getRealm\$realm().get(%s.class, proxyState.getRow\$realm().getLink(%s), false, Collections.<String>emptyList())", fieldTypeCanonicalName, fieldColKeyVariableReference(field))
            endMethod()
            emitEmptyLine()
            // Getter - End

            // Setter - Start
            val isEmbedded = Utils.isFieldTypeEmbedded(field.asType(), classCollection)
            val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field)
            val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field)
            emitAnnotation("Override")
            beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value")
                emitStatement("Realm realm = (Realm) proxyState.getRealm\$realm()")
                emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) {
                    // check excludeFields
                    beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString())
                        emitStatement("return")
                    endControlFlow()
                    beginControlFlow("if (value != null && !RealmObject.isManaged(value))")
                        if (isEmbedded) {
                            emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName)
                            emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap<RealmModel, RealmObjectProxy>(), Collections.EMPTY_SET)", linkedProxyClass)
                            emitStatement("value = proxyObject")
                        } else {
                            if (fieldTypeHasPrimaryKey(field.asType(), classCollection)) {
                                emitStatement("value = realm.copyToRealmOrUpdate(value)")
                            } else {
                                emitStatement("value = realm.copyToRealm(value)")
                            }
                        }
                    endControlFlow()

                    // set value as default value
                    emitStatement("final Row row = proxyState.getRow\$realm()")
                    beginControlFlow("if (value == null)")
                        emitSingleLineComment("Table#nullifyLink() does not support default value. Just using Row.")
                        emitStatement("row.nullifyLink(%s)", fieldColKeyVariableReference(field))
                        emitStatement("return")
                    endControlFlow()
                    emitStatement("proxyState.checkValidObject(value)")
                    emitStatement("row.getTable().setLink(%s, row.getObjectKey(), ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey(), true)", fieldColKeyVariableReference(field))
                    emitStatement("return")
                }
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                beginControlFlow("if (value == null)")
                    emitStatement("proxyState.getRow\$realm().nullifyLink(%s)", fieldColKeyVariableReference(field))
                    emitStatement("return")
                endControlFlow()

                if (isEmbedded) {
                    beginControlFlow("if (RealmObject.isManaged(value))")
                        emitStatement("proxyState.checkValidObject(value)")
                    endControlFlow()
                    emitStatement("%1\$s proxyObject = realm.createEmbeddedObject(%1\$s.class, this, \"%2\$s\")", linkedQualifiedClassName, fieldName)
                    emitStatement("%s.updateEmbeddedObject(realm, value, proxyObject, new HashMap<RealmModel, RealmObjectProxy>(), Collections.EMPTY_SET)", linkedProxyClass)
                } else {
                    emitStatement("proxyState.checkValidObject(value)")
                    emitStatement("proxyState.getRow\$realm().setLink(%s, ((RealmObjectProxy) value).realmGet\$proxyState().getRow\$realm().getObjectKey())", fieldColKeyVariableReference(field))
                }
            endMethod()
            // Setter - End
        }
    }

    @Throws(IOException::class)
    private fun emitRealmDictionary(
            writer: JavaWriter,
            field: VariableElement,
            fieldName: String,
            fieldTypeCanonicalName: String,
            valueTypeMirror: TypeMirror
    ) {
        val forRealmAny = Utils.isRealmAny(valueTypeMirror)
        val forRealmModel = Utils.isRealmModel(valueTypeMirror)

        with(writer) {
            val genericType: QualifiedClassName? = Utils.getGenericTypeQualifiedName(field)

            // Getter
            emitAnnotation("Override")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                emitSingleLineComment("use the cached value if available")
                beginControlFlow("if (${fieldName}RealmDictionary != null)")
                    emitStatement("return ${fieldName}RealmDictionary")
                nextControlFlow("else")

                    if (forRealmAny) {
                        emitStatement("OsMap osMap = proxyState.getRow\$realm().getRealmAnyMap(%s)", fieldColKeyVariableReference(field))
                    } else if (forRealmModel) {
                        emitStatement("OsMap osMap = proxyState.getRow\$realm().getModelMap(%s)", fieldColKeyVariableReference(field))
                    } else {
                        emitStatement("OsMap osMap = proxyState.getRow\$realm().getValueMap(%s, RealmFieldType.%s)", fieldColKeyVariableReference(field), Utils.getValueDictionaryFieldType(field).name)
                    }

                    emitStatement("${fieldName}RealmDictionary = new RealmDictionary<%s>(proxyState.getRealm\$realm(), osMap, %s.class)", genericType, genericType)
                    emitStatement("return ${fieldName}RealmDictionary")
                endControlFlow()
            endMethod()
            emitEmptyLine()

            // Setter
            emitAnnotation("Override")
            beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value")
                emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) emitter@{
                    // check excludeFields
                    beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%s\"))", field.simpleName.toString())
                        emitStatement("return")
                    endControlFlow()

                    // Either realmAny, which may or may not contain RealmObjects inside...
                    if (forRealmAny) {
                        emitSingleLineComment("if the dictionary contains unmanaged RealmModel instances boxed in RealmAny objects, convert them to managed.")
                        beginControlFlow("if (value != null && !value.isManaged())")
                            emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()")
                            emitStatement("final RealmDictionary<%s> original = value", genericType)
                            emitStatement("value = new RealmDictionary<%s>()", genericType)
                            beginControlFlow("for (java.util.Map.Entry<String, %s> item : original.entrySet())", genericType)
                                emitStatement("String entryKey = item.getKey()")
                                emitStatement("%s entryValue = item.getValue()", genericType)
                                emitSingleLineComment("ensure (potential) RealmModel instances are copied to Realm if generic type is RealmAny")
                                beginControlFlow("if (entryValue == null)")
                                    emitStatement("value.put(entryKey, null)")
                                nextControlFlow("else if (entryValue.getType() == RealmAny.Type.OBJECT)")
                                    emitStatement("RealmModel realmModel = entryValue.asRealmModel(RealmModel.class)")
                                    emitStatement("RealmModel modelFromRealm = realm.copyToRealmOrUpdate(realmModel)")
                                    emitStatement("value.put(entryKey, RealmAny.valueOf(modelFromRealm))")
                                nextControlFlow("else")
                                    emitStatement("value.put(entryKey, entryValue)")
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    } else if (forRealmModel) {
                        // ... Or Realm Models
                        emitSingleLineComment("if the dictionary contains unmanaged RealmModel instances, convert them to managed.")
                        beginControlFlow("if (value != null && !value.isManaged())")
                            emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()")
                            emitStatement("final RealmDictionary<%s> original = value", genericType)
                            emitStatement("value = new RealmDictionary<%s>()", genericType)
                            beginControlFlow("for (java.util.Map.Entry<String, %s> entry : original.entrySet())", genericType)
                                emitStatement("String entryKey = entry.getKey()")
                                emitStatement("%s entryValue = entry.getValue()", genericType)
                                beginControlFlow("if (entryValue == null || RealmObject.isManaged(entryValue))")
                                    emitStatement("value.put(entryKey, entryValue)")
                                nextControlFlow("else")
                                    emitStatement("value.put(entryKey, realm.copyToRealmOrUpdate(entryValue))")
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                }

                emitStatement("proxyState.getRealm\$realm().checkIfValid()")

                if (forRealmAny) {
                    emitStatement("OsMap osMap = proxyState.getRow\$realm().getRealmAnyMap(%s)", fieldColKeyVariableReference(field))
                } else if (forRealmModel) {
                    emitStatement("OsMap osMap = proxyState.getRow\$realm().getModelMap(%s)", fieldColKeyVariableReference(field))
                } else {
                    emitStatement("OsMap osMap = proxyState.getRow\$realm().getValueMap(%s, RealmFieldType.%s)", fieldColKeyVariableReference(field), Utils.getValueDictionaryFieldType(field).name)
                }

                beginControlFlow("if (value == null)")
                    emitStatement("return")
                endControlFlow()
                emitStatement("osMap.clear()")
                beginControlFlow("for (java.util.Map.Entry<String, %s> item : value.entrySet())", genericType)
                    emitStatement("String entryKey = item.getKey()")
                    emitStatement("%s entryValue = item.getValue()", genericType)

                    if (forRealmAny) {
                        beginControlFlow("if (entryValue == null)")
                            emitStatement("osMap.put(entryKey, null)")
                        nextControlFlow("else")
                            emitStatement("osMap.putRealmAny(entryKey, ProxyUtils.copyToRealmIfNeeded(proxyState, entryValue).getNativePtr())")
                        endControlFlow()
                    } else if (forRealmModel) {
                        beginControlFlow("if (entryValue == null)")
                            emitStatement("osMap.put(entryKey, null)")
                        nextControlFlow("else")
                            emitStatement("proxyState.checkValidObject(entryValue)")
                            emitStatement("osMap.putRow(entryKey, ((RealmObjectProxy) entryValue).realmGet\$proxyState().getRow\$realm().getObjectKey())")
                        endControlFlow()
                    } else {
                        emitStatement("osMap.put(entryKey, entryValue)")
                    }

                endControlFlow()

            endMethod()
        }
    }

    @Throws(IOException::class)
    private fun emitRealmSet(
            writer: JavaWriter,
            field: VariableElement,
            fieldName: String,
            fieldTypeCanonicalName: String,
            valueTypeMirror: TypeMirror
    ) {
        val forRealmAny = Utils.isRealmAny(valueTypeMirror)
        val forRealmModel = Utils.isRealmModel(valueTypeMirror)

        with(writer) {
            val genericType: QualifiedClassName? = Utils.getGenericTypeQualifiedName(field)

            // Getter
            emitAnnotation("Override")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                emitSingleLineComment("use the cached value if available")
                beginControlFlow("if (${fieldName}RealmSet != null)")
                    emitStatement("return ${fieldName}RealmSet")
                nextControlFlow("else")

                    if (forRealmAny) {
                        emitStatement("OsSet osSet = proxyState.getRow\$realm().getRealmAnySet(%s)", fieldColKeyVariableReference(field))
                    } else if (forRealmModel) {
                        emitStatement("OsSet osSet = proxyState.getRow\$realm().getModelSet(%s)", fieldColKeyVariableReference(field))
                    } else {
                        emitStatement("OsSet osSet = proxyState.getRow\$realm().getValueSet(%s, RealmFieldType.%s)", fieldColKeyVariableReference(field), Utils.getValueSetFieldType(field).name)
                    }

                    emitStatement("${fieldName}RealmSet = new RealmSet<%s>(proxyState.getRealm\$realm(), osSet, %s.class)", genericType, genericType)
                    emitStatement("return ${fieldName}RealmSet")
                endControlFlow()
            endMethod()
            emitEmptyLine()

            // Setter
            emitAnnotation("Override")
            beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value")

            emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) emitter@{
                // check excludeFields
                beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%s\"))", field.simpleName.toString())
                    emitStatement("return")
                endControlFlow()

                // Either realmAny, which may or may not contain RealmObjects inside...
                if (forRealmAny) {
                    emitSingleLineComment("if the set contains unmanaged RealmModel instances boxed in RealmAny objects, convert them to managed.")
                    beginControlFlow("if (value != null && !value.isManaged())")
                        emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()")
                        emitStatement("final RealmSet<%s> original = value", genericType)
                        emitStatement("value = new RealmSet<%s>()", genericType)
                        beginControlFlow("for (%s item : original)", genericType)
                            emitStatement("value.add(ProxyUtils.copyToRealmIfNeeded(proxyState, item))")
                        endControlFlow()
                    endControlFlow()
                } else if (forRealmModel) {
                    // ... Or Realm Models
                    emitSingleLineComment("if the set contains unmanaged RealmModel instances, convert them to managed.")
                    beginControlFlow("if (value != null && !value.isManaged())")
                        emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()")
                        emitStatement("final RealmSet<%s> original = value", genericType)
                        emitStatement("value = new RealmSet<%s>()", genericType)
                        beginControlFlow("for (%s item : original)", genericType)
                            beginControlFlow("if (item == null || RealmObject.isManaged(item))")
                                emitStatement("value.add(item)")
                            nextControlFlow("else")
                                emitStatement("value.add(realm.copyToRealmOrUpdate(item))")
                            endControlFlow()
                        endControlFlow()
                    endControlFlow()
                }
            }

            emitStatement("proxyState.getRealm\$realm().checkIfValid()")

            when {
                forRealmAny -> {
                    emitStatement("OsSet osSet = proxyState.getRow\$realm().getRealmAnySet(${fieldColKeyVariableReference(field)})")
                }
                forRealmModel -> {
                    emitStatement("OsSet osSet = proxyState.getRow\$realm().getModelSet(%s)", fieldColKeyVariableReference(field))
                }
                else -> {
                    emitStatement("OsSet osSet = proxyState.getRow\$realm().getValueSet(%s, RealmFieldType.%s)", fieldColKeyVariableReference(field), Utils.getValueSetFieldType(field).name)
                }
            }

            beginControlFlow("if (value == null)")
                emitStatement("return")
            endControlFlow()
            emitSingleLineComment("We need to create a copy of the set before clearing as the input and target sets might be the same.")
            emitStatement("List<$genericType> unmanagedList = new ArrayList<>(value)")
            emitStatement("osSet.clear()")
            beginControlFlow("for (%s item : unmanagedList)", genericType)

            when {
                forRealmAny -> {
                    emitStatement("osSet.addRealmAny(ProxyUtils.copyToRealmIfNeeded(proxyState, item).getNativePtr())")
                }
                forRealmModel -> {
                    emitStatement("proxyState.checkValidObject(item)")
                    emitStatement("Row row\$realm = ((RealmObjectProxy) item).realmGet\$proxyState().getRow\$realm()")
                    emitStatement("osSet.addRow(row\$realm.getObjectKey())")
                }
                else -> {
                    emitStatement("osSet.add(item)")
                }
            }

            endControlFlow()

            endMethod()
        }
    }

    /**
     * Emit Set/Get methods for Realm Model Lists and Lists of primitives.
     */
    @Throws(IOException::class)
    private fun emitRealmList(
            writer: JavaWriter,
            field: VariableElement,
            fieldName: String,
            fieldTypeCanonicalName: String,
            elementTypeMirror: TypeMirror?) {

        val genericType: QualifiedClassName? = Utils.getGenericTypeQualifiedName(field)
        val forRealmModel: Boolean = Utils.isRealmModel(elementTypeMirror)
        val forRealmAny: Boolean = Utils.isRealmAny(elementTypeMirror)

        writer.apply {
            // Getter - Start
            emitAnnotation("Override")
            beginMethod(fieldTypeCanonicalName, metadata.getInternalGetter(fieldName), EnumSet.of(Modifier.PUBLIC))
                emitStatement("proxyState.getRealm\$realm().checkIfValid()")
                emitSingleLineComment("use the cached value if available")
                beginControlFlow("if (${fieldName}RealmList != null)")
                    emitStatement("return ${fieldName}RealmList")
                nextControlFlow("else")
                    if (Utils.isRealmModelList(field)) {
                        emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldColKeyVariableReference(field))
                    } else {
                        emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldColKeyVariableReference(field), Utils.getValueListFieldType(field).name)
                    }
                    emitStatement("${fieldName}RealmList = new RealmList<%s>(%s.class, osList, proxyState.getRealm\$realm())", genericType, genericType)
                    emitStatement("return ${fieldName}RealmList")
                endControlFlow()
            endMethod()
            emitEmptyLine()
            // Getter - End

            // Setter - Start
            emitAnnotation("Override")
            beginMethod("void", metadata.getInternalSetter(fieldName), EnumSet.of(Modifier.PUBLIC), fieldTypeCanonicalName, "value")
            emitCodeForUnderConstruction(writer, metadata.isPrimaryKey(field)) emitter@{
                // check excludeFields
                beginControlFlow("if (proxyState.getExcludeFields\$realm().contains(\"%1\$s\"))", field.simpleName.toString())
                emitStatement("return")
                endControlFlow()

                if (!forRealmModel) {
                    return@emitter
                }

                emitSingleLineComment("if the list contains unmanaged RealmObjects, convert them to managed.")
                beginControlFlow("if (value != null && !value.isManaged())")
                    emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()")
                    emitStatement("final RealmList<%1\$s> original = value", genericType)
                    emitStatement("value = new RealmList<%1\$s>()", genericType)
                    beginControlFlow("for (%1\$s item : original)", genericType)
                        beginControlFlow("if (item == null || RealmObject.isManaged(item))")
                            emitStatement("value.add(item)")
                        nextControlFlow("else")
                            val type = getGenericType(field) ?:
                                throw IllegalArgumentException("Unable to derive generic type of ${fieldName}")
                            if (fieldTypeHasPrimaryKey(type, classCollection)) {
                                emitStatement("value.add(realm.copyToRealmOrUpdate(item))")
                            } else {
                                emitStatement("value.add(realm.copyToRealm(item))");
                            }
                        endControlFlow()
                    endControlFlow()
                endControlFlow()

                // LinkView currently does not support default value feature. Just fallback to normal code.
            }

            emitStatement("proxyState.getRealm\$realm().checkIfValid()")
            if (Utils.isRealmModelList(field)) {
                emitStatement("OsList osList = proxyState.getRow\$realm().getModelList(%s)", fieldColKeyVariableReference(field))
            } else {
                emitStatement("OsList osList = proxyState.getRow\$realm().getValueList(%1\$s, RealmFieldType.%2\$s)", fieldColKeyVariableReference(field), Utils.getValueListFieldType(field).name)
            }
            if (forRealmModel) {
                // Model lists.
                emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.")
                    beginControlFlow("if (value != null && value.size() == osList.size())")
                        emitStatement("int objects = value.size()")
                        beginControlFlow("for (int i = 0; i < objects; i++)")
                            emitStatement("%s linkedObject = value.get(i)", genericType)
                            emitStatement("proxyState.checkValidObject(linkedObject)")
                            emitStatement("osList.setRow(i, ((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getObjectKey())")
                        endControlFlow()
                    nextControlFlow("else")
                        emitStatement("osList.removeAll()")
                        beginControlFlow("if (value == null)")
                            emitStatement("return")
                        endControlFlow()
                        emitStatement("int objects = value.size()")
                        beginControlFlow("for (int i = 0; i < objects; i++)")
                            emitStatement("%s linkedObject = value.get(i)", genericType)
                            emitStatement("proxyState.checkValidObject(linkedObject)")
                            emitStatement("osList.addRow(((RealmObjectProxy) linkedObject).realmGet\$proxyState().getRow\$realm().getObjectKey())")
                        endControlFlow()
                    endControlFlow()
            } else {
                if(forRealmAny){
                    beginControlFlow("if (value != null && !value.isManaged())")
                        emitStatement("final Realm realm = (Realm) proxyState.getRealm\$realm()")
                        emitStatement("final RealmList<RealmAny> original = value")
                        emitStatement("value = new RealmList<RealmAny>()")
                        beginControlFlow("for (int i = 0; i < original.size(); i++)")
                            emitStatement("value.add(ProxyUtils.copyToRealmIfNeeded(proxyState, original.get(i)))")
                        endControlFlow()
                    endControlFlow()
                }

                // Value lists
                emitStatement("osList.removeAll()")
                beginControlFlow("if (value == null)")
                    emitStatement("return")
                    endControlFlow()
                    beginControlFlow("for (%1\$s item : value)", genericType)
                        beginControlFlow("if (item == null)")
                            emitStatement(if (metadata.isElementNullable(field)) "osList.addNull()" else "throw new IllegalArgumentException(\"Storing 'null' into $fieldName' is not allowed by the schema.\")")
                        nextControlFlow("else")
                            emitStatement(getStatementForAppendingValueToOsList("osList", "item", elementTypeMirror))
                        endControlFlow()
                    endControlFlow()
            }
            endMethod()
            // Setter - End
        }
    }

    private fun getStatementForAppendingValueToOsList(
            osListVariableName: String,
            valueVariableName: String,
            elementTypeMirror: TypeMirror?): String {

        val typeUtils = processingEnvironment.typeUtils
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.STRING_MIRROR)) {
            return "$osListVariableName.addString($valueVariableName)"
        }
        if ((typeUtils.isSameType(elementTypeMirror, typeMirrors.LONG_MIRROR)
                        || typeUtils.isSameType(elementTypeMirror, typeMirrors.INTEGER_MIRROR)
                        || typeUtils.isSameType(elementTypeMirror, typeMirrors.SHORT_MIRROR)
                        || typeUtils.isSameType(elementTypeMirror, typeMirrors.BYTE_MIRROR))) {
            return "$osListVariableName.addLong($valueVariableName.longValue())"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BINARY_MIRROR)) {
            return "$osListVariableName.addBinary($valueVariableName)"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DATE_MIRROR)) {
            return "$osListVariableName.addDate($valueVariableName)"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.BOOLEAN_MIRROR)) {
            return "$osListVariableName.addBoolean($valueVariableName)"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DOUBLE_MIRROR)) {
            return "$osListVariableName.addDouble($valueVariableName.doubleValue())"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.FLOAT_MIRROR)) {
            return "$osListVariableName.addFloat($valueVariableName.floatValue())"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.DECIMAL128_MIRROR)) {
            return "$osListVariableName.addDecimal128($valueVariableName)"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.OBJECT_ID_MIRROR)) {
            return "$osListVariableName.addObjectId($valueVariableName)"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.UUID_MIRROR)) {
            return "$osListVariableName.addUUID($valueVariableName)"
        }
        if (typeUtils.isSameType(elementTypeMirror, typeMirrors.MIXED_MIRROR)) {
            return "$osListVariableName.addRealmAny($valueVariableName.getNativePtr())"
        }
        throw RuntimeException("unexpected element type: $elementTypeMirror")
    }

    @Throws(IOException::class)
    private fun emitCodeForUnderConstruction(writer: JavaWriter, isPrimaryKey: Boolean, emitCode: () -> Unit) {
        writer.apply {
            beginControlFlow("if (proxyState.isUnderConstruction())")
                if (isPrimaryKey) {
                    emitSingleLineComment("default value of the primary key is always ignored.")
                    emitStatement("return")
                } else {
                    beginControlFlow("if (!proxyState.getAcceptDefaultValue\$realm())")
                        emitStatement("return")
                    endControlFlow()
                    emitCode()
                }
            endControlFlow()
            emitEmptyLine()
        }
    }

    // Note that because of bytecode hackery, this method may run before the constructor!
    // It may even run before fields have been initialized.
    @Throws(IOException::class)
    private fun emitInjectContextMethod(writer: JavaWriter) {
        writer.apply {
            emitAnnotation("Override")
            beginMethod("void","realm\$injectObjectContext", EnumSet.of(Modifier.PUBLIC))
                beginControlFlow("if (this.proxyState != null)")
                    emitStatement("return")
                endControlFlow()
                emitStatement("final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get()")
                emitStatement("this.columnInfo = (%1\$s) context.getColumnInfo()", columnInfoClassName())
                emitStatement("this.proxyState = new ProxyState<%1\$s>(this)", qualifiedJavaClassName)
                emitStatement("proxyState.setRealm\$realm(context.getRealm())")
                emitStatement("proxyState.setRow\$realm(context.getRow())")
                emitStatement("proxyState.setAcceptDefaultValue\$realm(context.getAcceptDefaultValue())")
                emitStatement("proxyState.setExcludeFields\$realm(context.getExcludeFields())")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitBacklinkFieldAccessors(writer: JavaWriter) {
        for (backlink in metadata.backlinkFields) {
            val cacheFieldName = backlink.targetField + BACKLINKS_FIELD_EXTENSION
            val realmResultsType = "RealmResults<" + backlink.sourceClass + ">"
            when (backlink.exposeAsRealmResults) {
                true -> {
                    // Getter, no setter
                    writer.apply {
                        emitAnnotation("Override")
                        beginMethod(realmResultsType, metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC))
                        emitStatement("BaseRealm realm = proxyState.getRealm\$realm()")
                        emitStatement("realm.checkIfValid()")
                        emitStatement("proxyState.getRow\$realm().checkIfAttached()")
                        beginControlFlow("if ($cacheFieldName == null)")
                            emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\")", backlink.sourceClass, backlink.sourceField)
                        endControlFlow()
                        emitStatement("return $cacheFieldName")
                        endMethod()
                        emitEmptyLine()
                    }
                }
                false -> {
                    // Getter, no setter
                    writer.apply {
                        emitAnnotation("Override")
                        beginMethod(backlink.sourceClass.toString(), metadata.getInternalGetter(backlink.targetField), EnumSet.of(Modifier.PUBLIC))
                        emitStatement("BaseRealm realm = proxyState.getRealm\$realm()")
                        emitStatement("realm.checkIfValid()")
                        emitStatement("proxyState.getRow\$realm().checkIfAttached()")
                        beginControlFlow("if ($cacheFieldName == null)")
                            emitStatement("$cacheFieldName = RealmResults.createBacklinkResults(realm, proxyState.getRow\$realm(), %s.class, \"%s\").first()", backlink.sourceClass, backlink.sourceField)
                        endControlFlow()
                        emitStatement("return $cacheFieldName") // TODO: Figure out the exact API for this
                        endMethod()
                        emitEmptyLine()
                    }
                }
            }
        }
    }

    @Throws(IOException::class)
    private fun emitRealmObjectProxyImplementation(writer: JavaWriter) {
        writer.apply {
            emitAnnotation("Override")
            beginMethod("ProxyState<?>", "realmGet\$proxyState", EnumSet.of(Modifier.PUBLIC))
                emitStatement("return proxyState")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitCreateExpectedObjectSchemaInfo(writer: JavaWriter) {
        writer.apply {
            beginMethod("OsObjectSchemaInfo", "createExpectedObjectSchemaInfo", EnumSet.of(Modifier.PRIVATE, Modifier.STATIC))
                // Guess capacity for Arrays used by OsObjectSchemaInfo.
                // Used to prevent array resizing at runtime
                val persistedFields = metadata.fields.size
                val computedFields = metadata.backlinkFields.size
                val embeddedClass = if (metadata.embedded) "true" else "false"
                val publicClassName = if (simpleJavaClassName.name != internalClassName) "\"${simpleJavaClassName.name}\"" else "NO_ALIAS"
                emitStatement("OsObjectSchemaInfo.Builder builder = new OsObjectSchemaInfo.Builder($publicClassName, \"$internalClassName\", $embeddedClass, $persistedFields, $computedFields)")

                // For each field generate corresponding table index constant
                for (field in metadata.fields) {
                    val internalFieldName = field.internalFieldName
                    val publicFieldName = if (field.javaName == internalFieldName) "NO_ALIAS" else "\"${field.javaName}\""

                    when (val fieldType = getRealmTypeChecked(field)) {
                        Constants.RealmFieldType.NOTYPE -> {
                            // Perhaps this should fail quickly?
                        }
                        Constants.RealmFieldType.OBJECT -> {
                            val fieldTypeQualifiedName = Utils.getFieldTypeQualifiedName(field)
                            val internalClassName = Utils.getReferencedTypeInternalClassNameStatement(fieldTypeQualifiedName, classCollection)
                            emitStatement("builder.addPersistedLinkProperty(%s, \"%s\", RealmFieldType.OBJECT, %s)", publicFieldName, internalFieldName, internalClassName)
                        }
                        Constants.RealmFieldType.LIST -> {
                            val genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field)
                            val internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection)
                            emitStatement("builder.addPersistedLinkProperty(%s, \"%s\", RealmFieldType.LIST, %s)", publicFieldName, internalFieldName, internalClassName)
                        }
                        Constants.RealmFieldType.INTEGER_LIST,
                        Constants.RealmFieldType.BOOLEAN_LIST,
                        Constants.RealmFieldType.STRING_LIST,
                        Constants.RealmFieldType.BINARY_LIST,
                        Constants.RealmFieldType.DATE_LIST,
                        Constants.RealmFieldType.FLOAT_LIST,
                        Constants.RealmFieldType.DECIMAL128_LIST,
                        Constants.RealmFieldType.OBJECT_ID_LIST,
                        Constants.RealmFieldType.UUID_LIST,
                        Constants.RealmFieldType.MIXED_LIST,
                        Constants.RealmFieldType.DOUBLE_LIST -> {
                            val requiredFlag = if (metadata.isElementNullable(field)) "!Property.REQUIRED" else "Property.REQUIRED"
                            emitStatement("builder.addPersistedValueListProperty(%s, \"%s\", %s, %s)", publicFieldName, internalFieldName, fieldType.realmType, requiredFlag)
                        }
                        Constants.RealmFieldType.BACKLINK -> {
                            throw IllegalArgumentException("LinkingObject field should not be added to metadata")
                        }
                        Constants.RealmFieldType.INTEGER,
                        Constants.RealmFieldType.FLOAT,
                        Constants.RealmFieldType.DOUBLE,
                        Constants.RealmFieldType.BOOLEAN,
                        Constants.RealmFieldType.STRING,
                        Constants.RealmFieldType.DATE,
                        Constants.RealmFieldType.BINARY,
                        Constants.RealmFieldType.DECIMAL128,
                        Constants.RealmFieldType.OBJECT_ID,
                        Constants.RealmFieldType.UUID,
                        Constants.RealmFieldType.MIXED,
                        Constants.RealmFieldType.REALM_INTEGER -> {
                            val nullableFlag = (if (metadata.isNullable(field)) "!" else "") + "Property.REQUIRED"
                            val indexedFlag = (if (metadata.isIndexed(field)) "" else "!") + "Property.INDEXED"
                            val primaryKeyFlag = (if (metadata.isPrimaryKey(field)) "" else "!") + "Property.PRIMARY_KEY"
                            emitStatement("builder.addPersistedProperty(%s, \"%s\", %s, %s, %s, %s)", publicFieldName, internalFieldName, fieldType.realmType, primaryKeyFlag, indexedFlag, nullableFlag)
                        }
                        Constants.RealmFieldType.STRING_TO_BOOLEAN_MAP,
                        Constants.RealmFieldType.STRING_TO_STRING_MAP,
                        Constants.RealmFieldType.STRING_TO_INTEGER_MAP,
                        Constants.RealmFieldType.STRING_TO_FLOAT_MAP,
                        Constants.RealmFieldType.STRING_TO_DOUBLE_MAP,
                        Constants.RealmFieldType.STRING_TO_BINARY_MAP,
                        Constants.RealmFieldType.STRING_TO_DATE_MAP,
                        Constants.RealmFieldType.STRING_TO_DECIMAL128_MAP,
                        Constants.RealmFieldType.STRING_TO_OBJECT_ID_MAP,
                        Constants.RealmFieldType.STRING_TO_UUID_MAP,
                        Constants.RealmFieldType.STRING_TO_MIXED_MAP -> {
                            val valueNullable = metadata.isDictionaryValueNullable(field)
                            val requiredFlag = if (valueNullable) "!Property.REQUIRED" else "Property.REQUIRED"
                            emitStatement("builder.addPersistedMapProperty(%s, \"%s\", %s, %s)", publicFieldName, internalFieldName, fieldType.realmType, requiredFlag)
                        }
                        Constants.RealmFieldType.STRING_TO_LINK_MAP -> {
                            val genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field)
                            val internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection)
                            emitStatement("builder.addPersistedLinkProperty(%s, \"%s\", RealmFieldType.STRING_TO_LINK_MAP, %s)", publicFieldName, internalFieldName, internalClassName)
                        }
                        Constants.RealmFieldType.BOOLEAN_SET,
                        Constants.RealmFieldType.STRING_SET,
                        Constants.RealmFieldType.INTEGER_SET,
                        Constants.RealmFieldType.FLOAT_SET,
                        Constants.RealmFieldType.DOUBLE_SET,
                        Constants.RealmFieldType.BINARY_SET,
                        Constants.RealmFieldType.DATE_SET,
                        Constants.RealmFieldType.DECIMAL128_SET,
                        Constants.RealmFieldType.OBJECT_ID_SET,
                        Constants.RealmFieldType.UUID_SET,
                        Constants.RealmFieldType.MIXED_SET -> {
                            val valueNullable = metadata.isSetValueNullable(field)
                            val requiredFlag = if (valueNullable) "!Property.REQUIRED" else "Property.REQUIRED"
                            emitStatement("builder.addPersistedSetProperty(%s, \"%s\", %s, %s)", publicFieldName, internalFieldName, fieldType.realmType, requiredFlag)
                        }
                        Constants.RealmFieldType.LINK_SET -> {
                            val genericTypeQualifiedName = Utils.getGenericTypeQualifiedName(field)
                            val internalClassName = Utils.getReferencedTypeInternalClassNameStatement(genericTypeQualifiedName, classCollection)
                            emitStatement("builder.addPersistedLinkProperty(${publicFieldName}, \"${internalFieldName}\", RealmFieldType.LINK_SET, ${internalClassName})")
                        }
                    }
                }
                for (backlink in metadata.backlinkFields) {
                    // Backlinks can only be created between classes in the current round of annotation processing
                    // as the forward link cannot be created unless you know the type already.
                    val sourceClass = classCollection.getClassFromQualifiedName(backlink.sourceClass!!)
                    val targetField = backlink.targetField // Only in the model, so no internal name exists
                    val internalSourceField = sourceClass.getInternalFieldName(backlink.sourceField!!)
                    emitStatement("""builder.addComputedLinkProperty("%s", "%s", "%s")""", targetField, sourceClass.internalClassName, internalSourceField)
                }
                emitStatement("return builder.build()")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitGetExpectedObjectSchemaInfo(writer: JavaWriter) {
        writer.apply {
            beginMethod("OsObjectSchemaInfo", "getExpectedObjectSchemaInfo", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC))
                emitStatement("return expectedObjectSchemaInfo")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitCreateColumnInfoMethod(writer: JavaWriter) {
        writer.apply {
            beginMethod(columnInfoClassName(), "createColumnInfo", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), "OsSchemaInfo", "schemaInfo")
                emitStatement("return new %1\$s(schemaInfo)", columnInfoClassName())
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitGetSimpleClassNameMethod(writer: JavaWriter) {
        writer.apply {
            beginMethod("String", "getSimpleClassName", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC))
                emitStatement("return \"%s\"", internalClassName)
            endMethod()
            emitEmptyLine()

            // Helper class for the annotation processor so it can access the internal class name
            // without needing to load the parent class (which we cannot do as it transitively loads
            // native code, which cannot be loaded on the JVM).
            beginType("ClassNameHelper", "class", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL))
                emitField("String", "INTERNAL_CLASS_NAME", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL), "\"$internalClassName\"")
            endType()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitNewProxyInstance(writer: JavaWriter) {
        writer.apply {
            beginMethod(generatedClassName, "newProxyInstance", EnumSet.of(Modifier.STATIC), "BaseRealm", "realm", "Row", "row")
                emitSingleLineComment("Ignore default values to avoid creating unexpected objects from RealmModel/RealmList fields")
                emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()")
                emitStatement("objectContext.set(realm, row, realm.getSchema().getColumnInfo(%s.class), false, Collections.<String>emptyList())", qualifiedJavaClassName)
                emitStatement("%1\$s obj = new %1\$s()", generatedClassName)
                emitStatement("objectContext.clear()")
                emitStatement("return obj")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitCopyOrUpdateMethod(writer: JavaWriter) {
        writer.apply {
            beginMethod(qualifiedJavaClassName,"copyOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC),
                    "Realm", "realm",
                    columnInfoClassName(), "columnInfo",
                    qualifiedJavaClassName.toString(), "object",
                    "boolean", "update",
                    "Map<RealmModel,RealmObjectProxy>", "cache",
                    "Set<ImportFlag>", "flags")

                beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null)")
                    emitStatement("final BaseRealm otherRealm = ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm()")
                    beginControlFlow("if (otherRealm.threadId != realm.threadId)")
                        emitStatement("throw new IllegalArgumentException(\"Objects which belong to Realm instances in other threads cannot be copied into this Realm instance.\")")
                    endControlFlow()

                    // If object is already in the Realm there is nothing to update
                    beginControlFlow("if (otherRealm.getPath().equals(realm.getPath()))")
                        emitStatement("return object")
                    endControlFlow()
                endControlFlow()
                emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()")
                emitStatement("RealmObjectProxy cachedRealmObject = cache.get(object)")
                beginControlFlow("if (cachedRealmObject != null)")
                    emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName)
                endControlFlow()
                emitEmptyLine()

                if (!metadata.hasPrimaryKey()) {
                    emitStatement("return copy(realm, columnInfo, object, update, cache, flags)")
                } else {
                    emitStatement("%s realmObject = null", qualifiedJavaClassName)
                    emitStatement("boolean canUpdate = update")
                    beginControlFlow("if (canUpdate)")
                        emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
                        emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey))

                        val primaryKeyGetter = metadata.primaryKeyGetter
                        val primaryKeyElement = metadata.primaryKey
                        if (metadata.isNullable(primaryKeyElement!!)) {
                            if (Utils.isString(primaryKeyElement)) {
                                emitStatement("String value = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                                emitStatement("long objKey = Table.NO_MATCH")
                                beginControlFlow("if (value == null)")
                                    emitStatement("objKey = table.findFirstNull(pkColumnKey)")
                                nextControlFlow("else")
                                    emitStatement("objKey = table.findFirstString(pkColumnKey, value)")
                                endControlFlow()
                            } else if (Utils.isObjectId(primaryKeyElement)) {
                                emitStatement("org.bson.types.ObjectId value = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                                emitStatement("long objKey = Table.NO_MATCH")
                                beginControlFlow("if (value == null)")
                                    emitStatement("objKey = table.findFirstNull(pkColumnKey)")
                                nextControlFlow("else")
                                    emitStatement("objKey = table.findFirstObjectId(pkColumnKey, value)")
                                endControlFlow()
                            } else if (Utils.isUUID(primaryKeyElement)) {
                                emitStatement("java.util.UUID value = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                                emitStatement("long objKey = Table.NO_MATCH")
                                beginControlFlow("if (value == null)")
                                    emitStatement("objKey = table.findFirstNull(pkColumnKey)")
                                nextControlFlow("else")
                                    emitStatement("objKey = table.findFirstUUID(pkColumnKey, value)")
                                endControlFlow()
                            } else {
                                emitStatement("Number value = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                                emitStatement("long objKey = Table.NO_MATCH")
                                beginControlFlow("if (value == null)")
                                    emitStatement("objKey = table.findFirstNull(pkColumnKey)")
                                nextControlFlow("else")
                                    emitStatement("objKey = table.findFirstLong(pkColumnKey, value.longValue())")
                                endControlFlow()
                            }
                        } else {
                            if (Utils.isString(primaryKeyElement)) {
                                emitStatement("long objKey = table.findFirstString(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                            } else if (Utils.isObjectId(primaryKeyElement)) {
                                emitStatement("long objKey = table.findFirstObjectId(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                            } else if (Utils.isUUID(primaryKeyElement)) {
                                emitStatement("long objKey = table.findFirstUUID(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                            } else {
                                emitStatement("long objKey = table.findFirstLong(pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                            }
                        }

                        beginControlFlow("if (objKey == Table.NO_MATCH)")
                            emitStatement("canUpdate = false")
                        nextControlFlow("else")
                            beginControlFlow("try")
                                emitStatement("objectContext.set(realm, table.getUncheckedRow(objKey), columnInfo, false, Collections.<String> emptyList())")
                                emitStatement("realmObject = new %s()", generatedClassName)
                                emitStatement("cache.put(object, (RealmObjectProxy) realmObject)")
                            nextControlFlow("finally")
                                emitStatement("objectContext.clear()")
                            endControlFlow()
                        endControlFlow()
                    endControlFlow()
                    emitEmptyLine()
                    emitStatement("return (canUpdate) ? update(realm, columnInfo, realmObject, object, cache, flags) : copy(realm, columnInfo, object, update, cache, flags)")
                }

            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun setTableValues(writer: JavaWriter, fieldType: String, fieldName: String, interfaceName: SimpleClassName, getter: String, isUpdate: Boolean) {
        writer.apply {
            when(fieldType) {
                "long",
                "int",
                "short",
                "byte" -> {
                    emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter)
                }
                "java.lang.Long",
                "java.lang.Integer",
                "java.lang.Short",
                "java.lang.Byte" -> {
                    emitStatement("Number %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, objKey, %s.longValue(), false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "io.realm.MutableRealmInteger" -> {
                    emitStatement("Long %s = ((%s) object).%s().get()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetLong(tableNativePtr, columnInfo.%sColKey, objKey, %s.longValue(), false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                            emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "double" -> {
                    emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter)
                }
                "java.lang.Double" -> {
                    emitStatement("Double %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetDouble(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "float" -> {
                    emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter)
                }
                "java.lang.Float" -> {
                    emitStatement("Float %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetFloat(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "boolean" -> {
                    emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, objKey, ((%s) object).%s(), false)", fieldName, interfaceName, getter)
                }
                "java.lang.Boolean" -> {
                    emitStatement("Boolean %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetBoolean(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "byte[]" -> {
                    emitStatement("byte[] %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetByteArray(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "java.util.Date" -> {
                    emitStatement("java.util.Date %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetTimestamp(tableNativePtr, columnInfo.%sColKey, objKey, %s.getTime(), false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "java.lang.String" -> {
                    emitStatement("String %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetString(tableNativePtr, columnInfo.%sColKey, objKey, %s, false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                                emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "org.bson.types.Decimal128" -> {
                    emitStatement("org.bson.types.Decimal128 %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetDecimal128(tableNativePtr, columnInfo.%1\$sColKey, objKey, %2\$s.getLow(), %2\$s.getHigh(), false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                            emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "org.bson.types.ObjectId" -> {
                    emitStatement("org.bson.types.ObjectId %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetObjectId(tableNativePtr, columnInfo.%sColKey, objKey, %s.toString(), false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                            emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                "java.util.UUID" -> {
                    emitStatement("java.util.UUID %s = ((%s) object).%s()", getter, interfaceName, getter)
                    beginControlFlow("if (%s != null)", getter)
                        emitStatement("Table.nativeSetUUID(tableNativePtr, columnInfo.%sColKey, objKey, %s.toString(), false)", fieldName, getter)
                        if (isUpdate) {
                            nextControlFlow("else")
                            emitStatement("Table.nativeSetNull(tableNativePtr, columnInfo.%sColKey, objKey, false)", fieldName)
                        }
                    endControlFlow()
                }
                else -> {
                    throw IllegalStateException("Unsupported type $fieldType")
                }
            }
        }
    }

    @Throws(IOException::class)
    private fun emitInsertInternal(writer: JavaWriter){
        writer.apply {
            addPrimaryKeyCheckIfNeeded(metadata, true, writer)
            for (field in metadata.fields) {
                val fieldName = field.simpleName.toString()
                val fieldType = QualifiedClassName(field.asType().toString())
                val getter = metadata.getInternalGetter(fieldName)

                when {
                    Utils.isRealmModel(field) -> {
                        val isEmbedded = Utils.isFieldTypeEmbedded(field.asType(), classCollection)
                        emitEmptyLine()
                        emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter)
                        beginControlFlow("if (%sObj != null)", fieldName)
                            emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName)
                            if (isEmbedded) {
                                beginControlFlow("if (cache%s != null)", fieldName)
                                    emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName)
                                nextControlFlow("else")
                                    emitStatement("cache%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName)
                                endControlFlow()
                            } else {
                                beginControlFlow("if (cache%s == null)", fieldName)
                                    emitStatement("cache%s = %s.insert(realm, %sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName)
                                endControlFlow()
                                emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName)
                            }
                        endControlFlow()
                    }
                    Utils.isRealmModelList(field) -> {
                        val genericType: TypeMirror = Utils.getGenericType(field)!!
                        val isEmbedded = Utils.isFieldTypeEmbedded(genericType, classCollection)
                        emitEmptyLine()
                        emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
                        beginControlFlow("if (%sList != null)", fieldName)
                            emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                            beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
                                emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                if (isEmbedded) {
                                    beginControlFlow("if (cacheItemIndex%s != null)", fieldName)
                                        emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName)
                                    nextControlFlow("else")
                                        emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName)
                                    endControlFlow()
                                } else {
                                    beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                        emitStatement("cacheItemIndex%1\$s = %2\$s.insert(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field))
                                    endControlFlow()
                                    emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName)
                                }
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmValueList(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field)
                        emitEmptyLine()
                        emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
                        beginControlFlow("if (%sList != null)", fieldName)
                            emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                            beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
                                beginControlFlow("if (%1\$sItem == null)", fieldName)
                                    emitStatement(fieldName + "OsList.addNull()")
                                nextControlFlow("else")
                                    emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror))
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmAny(field) -> {
                        emitEmptyLine()

                        emitStatement("RealmAny ${fieldName}RealmAny = ((${interfaceName}) object).${getter}()")
                        emitStatement("${fieldName}RealmAny = ProxyUtils.insert(${fieldName}RealmAny, realm, cache)")
                        emitStatement("Table.nativeSetRealmAny(tableNativePtr, columnInfo.${fieldName}ColKey, objKey, ${fieldName}RealmAny.getNativePtr(), false)")
                    }
                    Utils.isRealmAnyList(field) -> {
                        emitEmptyLine()

                        emitStatement("RealmList<RealmAny> ${fieldName}UnmanagedList = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedList != null)")
                            emitStatement("OsList ${fieldName}OsList = new OsList(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            beginControlFlow("for (int i = 0; i < ${fieldName}UnmanagedList.size(); i++)")
                                emitStatement("RealmAny realmAnyItem = ${fieldName}UnmanagedList.get(i)")
                                emitStatement("realmAnyItem = ProxyUtils.insert(realmAnyItem, realm, cache)")
                                emitStatement("${fieldName}OsList.addRealmAny(realmAnyItem.getNativePtr())")
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmValueDictionary(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        val elementTypeMirror = TypeMirrors.getRealmDictionaryElementTypeMirror(field)
                        emitEmptyLine()
                        emitStatement("RealmDictionary<${genericType}> ${fieldName}UnmanagedDictionary = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedDictionary != null)", fieldName)
                            emitStatement("OsMap ${fieldName}OsMap = new OsMap(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")

                            emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                            beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                emitStatement("String entryKey = entry.getKey()")
                                emitStatement("$genericType ${fieldName}UnmanagedEntryValue = entry.getValue()")
                                emitStatement("${fieldName}OsMap.put(entryKey, ${fieldName}UnmanagedEntryValue)")
                        endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmAnyDictionary(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        emitStatement("RealmDictionary<RealmAny> ${fieldName}UnmanagedDictionary = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                            emitStatement("OsMap ${fieldName}OsMap = new OsMap(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                            emitStatement("java.util.List<String> keys = new java.util.ArrayList<>()")
                            emitStatement("java.util.List<Long> realmAnyPointers = new java.util.ArrayList<>()")
                            beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                emitStatement("RealmAny realmAnyItem = entry.getValue()")
                                emitStatement("realmAnyItem = ProxyUtils.insert(realmAnyItem, realm, cache)")
                                emitStatement("${fieldName}OsMap.putRealmAny(entry.getKey(), realmAnyItem.getNativePtr())")
                            endControlFlow()
                        endControlFlow()
                        emitEmptyLine()
                    }
                    Utils.isRealmModelDictionary(field) -> {
                        val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!!
                        val listElementType: TypeMirror = Utils.getGenericType(field)!!
                        val isEmbedded = Utils.isFieldTypeEmbedded(listElementType, classCollection)
                        val linkedProxyClass: SimpleClassName = Utils.getDictionaryGenericProxyClassSimpleName(field)

                        emitStatement("RealmDictionary<${genericType}> ${fieldName}UnmanagedDictionary = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                        emitStatement("OsMap ${fieldName}OsMap = new OsMap(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                            beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                emitStatement("String entryKey = entry.getKey()")
                                emitStatement("$genericType ${fieldName}UnmanagedEntryValue = entry.getValue()")
                                beginControlFlow("if(${fieldName}UnmanagedEntryValue == null)")
                                    emitStatement("${fieldName}OsMap.put(entryKey, null)")
                                nextControlFlow("else")
                                    // here goes the rest
                                    emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}UnmanagedEntryValue)")
                                    if (isEmbedded) {
                                        beginControlFlow("if (cacheItemIndex${fieldName} != null)")
                                            emitStatement("""throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache${fieldName}.toString()")""")
                                        nextControlFlow("else")
                                            emitStatement("cacheItemIndex${fieldName} = ${linkedProxyClass}.insert(realm, table, columnInfo.${fieldName}ColKey, objKey, ${fieldName}UnmanagedEntryValue, cache)")
                                        endControlFlow()
                                        emitStatement("${fieldName}OsMap.putRow(entryKey, cacheItemIndex${fieldName})")
                                    } else {
                                        beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                            emitStatement("cacheItemIndex${fieldName} = ${linkedProxyClass}.insert(realm, ${fieldName}UnmanagedEntryValue, cache)")
                                        endControlFlow()
                                        emitStatement("${fieldName}OsMap.putRow(entryKey, cacheItemIndex${fieldName})")
                                    }
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmModelSet(field) -> {
                        val genericType: TypeMirror = Utils.getGenericType(field)!!
                        val isEmbedded = Utils.isFieldTypeEmbedded(genericType, classCollection)
                        emitEmptyLine()
                        emitStatement("RealmSet<${genericType}> ${fieldName}Set = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}Set != null)")
                            emitStatement("OsSet ${fieldName}OsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            beginControlFlow("for (${genericType} ${fieldName}Item: ${fieldName}Set)")
                                emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
                                if (isEmbedded) {
                                    emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex${fieldName}.toString())")
                                } else {
                                    beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                        emitStatement("cacheItemIndex${fieldName} = ${Utils.getSetGenericProxyClassSimpleName(field)}.insert(realm, ${fieldName}Item, cache)")
                                    endControlFlow()
                                    emitStatement("${fieldName}OsSet.addRow(cacheItemIndex${fieldName})")
                                }
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmValueSet(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        emitEmptyLine()
                        emitStatement("RealmSet<${genericType}> ${fieldName}Set = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}Set != null)")
                            emitStatement("OsSet ${fieldName}OsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            beginControlFlow("for (${genericType} ${fieldName}Item: ${fieldName}Set)")
                                beginControlFlow("if (${fieldName}Item == null)")
                                    emitStatement(fieldName + "OsSet.add(($genericType) null)")
                                nextControlFlow("else")
                                    emitStatement("${fieldName}OsSet.add(${fieldName}Item)")
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmAnySet(field) -> {
                        emitEmptyLine()

                        emitStatement("RealmSet<RealmAny> ${fieldName}UnmanagedSet = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedSet != null)")
                            emitStatement("OsSet ${fieldName}OsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            beginControlFlow("for (RealmAny realmAnyItem: ${fieldName}UnmanagedSet)")
                                emitStatement("realmAnyItem = ProxyUtils.insert(realmAnyItem, realm, cache)")
                                emitStatement("${fieldName}OsSet.addRealmAny(realmAnyItem.getNativePtr())")
                            endControlFlow()
                        endControlFlow()
                    }
                    else -> {
                        if (metadata.primaryKey !== field) {
                            setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, false)
                        }
                    }
                }
            }
        }
    }

    @Throws(IOException::class)
    private fun emitInsertMethod(writer: JavaWriter) {
        writer.apply {
            val topLevelArgs = arrayOf("Realm", "realm",
                    qualifiedJavaClassName.toString(), "object",
                    "Map<RealmModel,Long>", "cache")
            val embeddedArgs = arrayOf("Realm", "realm",
                    "Table", "parentObjectTable",
                    "long", "parentColumnKey",
                    "long", "parentObjectKey",
                    qualifiedJavaClassName.toString(), "object",
                    "Map<RealmModel,Long>", "cache")
            val args = if (metadata.embedded) embeddedArgs else topLevelArgs
            beginMethod("long","insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args)

            // If object is already in the Realm there is nothing to update, unless it is an embedded
            // object. In which case we always update the underlying object.
            if (!metadata.embedded) {
                beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))")
                   emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()")
                endControlFlow()
            }

            emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
            emitStatement("long tableNativePtr = table.getNativePtr()")
            emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName)

            if (metadata.hasPrimaryKey()) {
                emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey))
            }

            emitInsertInternal(this)

            emitStatement("return objKey")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitInsertListMethod(writer: JavaWriter) {
        writer.apply {
            val topLevelArgs = arrayOf("Realm", "realm",
                    "Iterator<? extends RealmModel>", "objects",
                    "Map<RealmModel,Long>", "cache")
            val embeddedArgs = arrayOf("Realm", "realm",
                    "Table", "parentObjectTable",
                    "long", "parentColumnKey",
                    "long", "parentObjectKey",
                    "Iterator<? extends RealmModel>", "objects",
                    "Map<RealmModel,Long>", "cache")
            val args = if (metadata.embedded) embeddedArgs else topLevelArgs

            beginMethod("void", "insert", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args)
                emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
                emitStatement("long tableNativePtr = table.getNativePtr()")
                emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName)
                if (metadata.hasPrimaryKey()) {
                    emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey))
                }
                emitStatement("%s object = null", qualifiedJavaClassName)

                beginControlFlow("while (objects.hasNext())")
                    emitStatement("object = (%s) objects.next()", qualifiedJavaClassName)
                    beginControlFlow("if (cache.containsKey(object))")
                        emitStatement("continue")
                    endControlFlow()
                    beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))")
                        emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey())")
                        emitStatement("continue")
                    endControlFlow()

                    emitInsertInternal(this)
                endControlFlow()
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun insertOrUpdateInternal(writer: JavaWriter){
        writer.apply {
            addPrimaryKeyCheckIfNeeded(metadata, false, writer)
            for (field in metadata.fields) {
                val fieldName = field.simpleName.toString()
                val fieldType = QualifiedClassName(field.asType().toString())
                val getter = metadata.getInternalGetter(fieldName)

                when {
                    Utils.isRealmModel(field) -> {
                        val isEmbedded = Utils.isFieldTypeEmbedded(field.asType(), classCollection)
                        emitEmptyLine()
                        emitStatement("%s %sObj = ((%s) object).%s()", fieldType, fieldName, interfaceName, getter)
                        beginControlFlow("if (%sObj != null)", fieldName)
                            emitStatement("Long cache%1\$s = cache.get(%1\$sObj)", fieldName)
                            if (isEmbedded) {
                                beginControlFlow("if (cache%s != null)", fieldName)
                                    emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cache%s.toString())", fieldName)
                                nextControlFlow("else")
                                    emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field), fieldName)
                                endControlFlow()
                            } else {
                                beginControlFlow("if (cache%s == null)", fieldName)
                                    emitStatement("cache%1\$s = %2\$s.insertOrUpdate(realm, %1\$sObj, cache)", fieldName, Utils.getProxyClassSimpleName(field))
                                endControlFlow()
                                emitStatement("Table.nativeSetLink(tableNativePtr, columnInfo.%1\$sColKey, objKey, cache%1\$s, false)", fieldName)
                            }
                        nextControlFlow("else")
                            // No need to throw exception here if the field is not nullable. A exception will be thrown in setter.
                            emitStatement("Table.nativeNullifyLink(tableNativePtr, columnInfo.%sColKey, objKey)", fieldName)
                        endControlFlow()
                    }
                    Utils.isRealmModelList(field) -> {
                        val genericType: TypeMirror = Utils.getGenericType(field)!!
                        val isEmbedded = Utils.isFieldTypeEmbedded(genericType, classCollection)

                        emitEmptyLine()
                        emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                        emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
                        if (isEmbedded) {
                            emitStatement("%1\$sOsList.removeAll()", fieldName)
                            beginControlFlow("if (%sList != null)", fieldName)
                                beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
                                    emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                    beginControlFlow("if (cacheItemIndex%s != null)", fieldName)
                                        emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: \" + cacheItemIndex%s.toString())", fieldName)
                                    nextControlFlow("else")
                                        emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, table, columnInfo.%3\$sColKey, objKey, %3\$sItem, cache)", fieldName, Utils.getProxyClassName(QualifiedClassName(genericType.toString())), fieldName)
                                    endControlFlow()
                                endControlFlow()
                            endControlFlow()
                        } else {
                            beginControlFlow("if (%1\$sList != null && %1\$sList.size() == %1\$sOsList.size())", fieldName)
                                emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.")
                                emitStatement("int objectCount = %1\$sList.size()", fieldName)
                                beginControlFlow("for (int i = 0; i < objectCount; i++)")
                                    emitStatement("%1\$s %2\$sItem = %2\$sList.get(i)", genericType, fieldName)
                                    emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                    beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                        emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field))
                                    endControlFlow()
                                    emitStatement("%1\$sOsList.setRow(i, cacheItemIndex%1\$s)", fieldName)
                                endControlFlow()
                            nextControlFlow("else")
                                emitStatement("%1\$sOsList.removeAll()", fieldName)
                                beginControlFlow("if (%sList != null)", fieldName)
                                    beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
                                        emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                        beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                            emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getProxyClassSimpleName(field))
                                        endControlFlow()
                                        emitStatement("%1\$sOsList.addRow(cacheItemIndex%1\$s)", fieldName)
                                    endControlFlow()
                                endControlFlow()
                            endControlFlow()
                        }
                        emitEmptyLine()
                    }
                    Utils.isRealmValueList(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        val elementTypeMirror = TypeMirrors.getRealmListElementTypeMirror(field)
                        emitEmptyLine()
                        emitStatement("OsList %1\$sOsList = new OsList(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                        emitStatement("%1\$sOsList.removeAll()", fieldName)
                        emitStatement("RealmList<%s> %sList = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
                        beginControlFlow("if (%sList != null)", fieldName)
                           beginControlFlow("for (%1\$s %2\$sItem : %2\$sList)", genericType, fieldName)
                                beginControlFlow("if (%1\$sItem == null)", fieldName)
                                    emitStatement("%1\$sOsList.addNull()", fieldName)
                                nextControlFlow("else")
                                    emitStatement(getStatementForAppendingValueToOsList(fieldName + "OsList", fieldName + "Item", elementTypeMirror))
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                        emitEmptyLine()
                    }
                    Utils.isRealmAny(field) -> {
                        emitStatement("RealmAny ${fieldName}RealmAny = ((${interfaceName}) object).${getter}()")
                        emitStatement("${fieldName}RealmAny = ProxyUtils.insertOrUpdate(${fieldName}RealmAny, realm, cache)")
                        emitStatement("Table.nativeSetRealmAny(tableNativePtr, columnInfo.${fieldName}ColKey, objKey, ${fieldName}RealmAny.getNativePtr(), false)")
                    }
                    Utils.isRealmAnyList(field) -> {
                        emitEmptyLine()
                        emitStatement("OsList ${fieldName}OsList = new OsList(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                        emitStatement("RealmList<RealmAny> ${fieldName}List = ((${interfaceName}) object).${getter}()")

                        beginControlFlow("if (${fieldName}List != null && ${fieldName}List.size() == ${fieldName}OsList.size())")
                            emitSingleLineComment("For lists of equal lengths, we need to set each element directly as clearing the receiver list can be wrong if the input and target list are the same.")
                            emitStatement("int objectCount = ${fieldName}List.size()")
                            beginControlFlow("for (int i = 0; i < objectCount; i++)")
                                emitStatement("RealmAny ${fieldName}Item = ${fieldName}List.get(i)")
                                emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
                                beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                    emitStatement("${fieldName}Item = ProxyUtils.insertOrUpdate(${fieldName}Item, realm, cache)")
                                endControlFlow()
                                emitStatement("${fieldName}OsList.setRealmAny(i, ${fieldName}Item.getNativePtr())")
                            endControlFlow()
                        nextControlFlow("else")
                            emitStatement("${fieldName}OsList.removeAll()")
                            beginControlFlow("if (${fieldName}List != null)")
                                beginControlFlow("for (RealmAny ${fieldName}Item : ${fieldName}List)")
                                    emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
                                    beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                        emitStatement("${fieldName}Item = ProxyUtils.insertOrUpdate(${fieldName}Item, realm, cache)")
                                    endControlFlow()
                                    emitStatement("${fieldName}OsList.addRealmAny(${fieldName}Item.getNativePtr())")
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmModelDictionary(field) -> {
                        val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!!
                        val dictElementType: TypeMirror = Utils.getGenericType(field)!!
                        val isEmbedded = Utils.isFieldTypeEmbedded(dictElementType, classCollection)
                        val linkedProxyClass: SimpleClassName = Utils.getDictionaryGenericProxyClassSimpleName(field)

                        emitStatement("RealmDictionary<${genericType}> ${fieldName}UnmanagedDictionary = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                        emitStatement("OsMap ${fieldName}OsMap = new OsMap(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                            beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                emitStatement("String entryKey = entry.getKey()")
                                emitStatement("$genericType ${fieldName}UnmanagedEntryValue = entry.getValue()")
                                beginControlFlow("if(${fieldName}UnmanagedEntryValue == null)")
                                    emitStatement("${fieldName}OsMap.put(entryKey, null)")
                                nextControlFlow("else")
                                    // here goes the rest
                                    emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}UnmanagedEntryValue)")
                                    if (isEmbedded) {
                                        beginControlFlow("if (cacheItemIndex${fieldName} != null)")
                                            emitStatement("""throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache${fieldName}.toString()")""")
                                        nextControlFlow("else")
                                            emitStatement("cacheItemIndex${fieldName} = ${linkedProxyClass}.insertOrUpdate(realm, table, columnInfo.${fieldName}ColKey, objKey, ${fieldName}UnmanagedEntryValue, cache)")
                                        endControlFlow()
                                        emitStatement("${fieldName}OsMap.putRow(entryKey, cacheItemIndex${fieldName})")
                                    } else {
                                        beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                            emitStatement("cacheItemIndex${fieldName} = ${linkedProxyClass}.insertOrUpdate(realm, ${fieldName}UnmanagedEntryValue, cache)")
                                        endControlFlow()
                                        emitStatement("${fieldName}OsMap.putRow(entryKey, cacheItemIndex${fieldName})")
                                    }
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmValueDictionary(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        emitEmptyLine()
                        emitStatement("RealmDictionary<${genericType}> ${fieldName}UnmanagedDictionary = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedDictionary != null)", fieldName)
                            emitStatement("OsMap ${fieldName}OsMap = new OsMap(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")

                            emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                            beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                emitStatement("String entryKey = entry.getKey()")
                                emitStatement("$genericType ${fieldName}UnmanagedEntryValue = entry.getValue()")
                                emitStatement("${fieldName}OsMap.put(entryKey, ${fieldName}UnmanagedEntryValue)")
                        endControlFlow()
                        endControlFlow()
                    }
                    Utils.isRealmAnyDictionary(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        emitStatement("RealmDictionary<RealmAny> ${fieldName}UnmanagedDictionary = ((${interfaceName}) object).${getter}()")
                        beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                            emitStatement("OsMap ${fieldName}OsMap = new OsMap(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                            emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                            emitStatement("java.util.List<String> keys = new java.util.ArrayList<>()")
                            emitStatement("java.util.List<Long> realmAnyPointers = new java.util.ArrayList<>()")
                            beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                emitStatement("RealmAny realmAnyItem = entry.getValue()")
                                emitStatement("realmAnyItem = ProxyUtils.insertOrUpdate(realmAnyItem, realm, cache)")
                                emitStatement("${fieldName}OsMap.putRealmAny(entry.getKey(), realmAnyItem.getNativePtr())")
                            endControlFlow()
                        endControlFlow()
                        emitEmptyLine()
                    }
                    Utils.isRealmModelSet(field) -> {
                        val genericType: TypeMirror = Utils.getGenericType(field)!!
                        val isEmbedded = Utils.isFieldTypeEmbedded(genericType, classCollection)

                        emitEmptyLine()
                        emitStatement("OsSet %1\$sOsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                        emitStatement("RealmSet<%s> %sSet = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
                        if (isEmbedded) {
                            // throw not supported
                            throw UnsupportedOperationException("Field $fieldName of type RealmSet<${genericType}>, RealmSet does not support embedded objects.")
                        } else {
                            beginControlFlow("if (%1\$sSet != null && %1\$sSet.size() == %1\$sOsSet.size())", fieldName)
                                emitSingleLineComment("For Sets of equal lengths, we need to set each element directly as clearing the receiver Set can be wrong if the input and target Set are the same.")
                                emitStatement("int objectCount = %1\$sSet.size()", fieldName)
                                beginControlFlow("for (${genericType} ${fieldName}Item: ${fieldName}Set)")
                                    emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                    beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                        emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getSetGenericProxyClassSimpleName(field))
                                    endControlFlow()
                                    emitStatement("%1\$sOsSet.addRow(cacheItemIndex%1\$s)", fieldName)
                                endControlFlow()
                            nextControlFlow("else")
                                emitStatement("%1\$sOsSet.clear()", fieldName)
                                beginControlFlow("if (%sSet != null)", fieldName)
                                    beginControlFlow("for (%1\$s %2\$sItem : %2\$sSet)", genericType, fieldName)
                                        emitStatement("Long cacheItemIndex%1\$s = cache.get(%1\$sItem)", fieldName)
                                        beginControlFlow("if (cacheItemIndex%s == null)", fieldName)
                                            emitStatement("cacheItemIndex%1\$s = %2\$s.insertOrUpdate(realm, %1\$sItem, cache)", fieldName, Utils.getSetGenericProxyClassSimpleName(field))
                                        endControlFlow()
                                        emitStatement("%1\$sOsSet.addRow(cacheItemIndex%1\$s)", fieldName)
                                    endControlFlow()
                                endControlFlow()
                            endControlFlow()
                        }
                        emitEmptyLine()
                    }
                    Utils.isRealmValueSet(field) -> {
                        val genericType = Utils.getGenericTypeQualifiedName(field)
                        emitEmptyLine()
                        emitStatement("OsSet %1\$sOsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.%1\$sColKey)", fieldName)
                        emitStatement("%1\$sOsSet.clear()", fieldName)
                        emitStatement("RealmSet<%s> %sSet = ((%s) object).%s()", genericType, fieldName, interfaceName, getter)
                        beginControlFlow("if (%sSet != null)", fieldName)
                           beginControlFlow("for (%1\$s %2\$sItem : %2\$sSet)", genericType, fieldName)
                                beginControlFlow("if (%1\$sItem == null)", fieldName)
                                    emitStatement("%1\$sOsSet.add(($genericType) null)", fieldName)
                                nextControlFlow("else")
                                    emitStatement("${fieldName}OsSet.add(${fieldName}Item)")
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                        emitEmptyLine()
                    }
                    Utils.isRealmAnySet(field) -> {
                        emitEmptyLine()
                        emitStatement("OsSet ${fieldName}OsSet = new OsSet(table.getUncheckedRow(objKey), columnInfo.${fieldName}ColKey)")
                        emitStatement("RealmSet<RealmAny> ${fieldName}Set = ((${interfaceName}) object).${getter}()")

                        beginControlFlow("if (${fieldName}Set != null && ${fieldName}Set.size() == ${fieldName}OsSet.size())")
                            emitSingleLineComment("For Sets of equal lengths, we need to set each element directly as clearing the receiver Set can be wrong if the input and target Set are the same.")
                            emitStatement("int objectCount = ${fieldName}Set.size()")
                            beginControlFlow("for (RealmAny ${fieldName}Item: ${fieldName}Set)")
                                emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
                                beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                    emitStatement("${fieldName}Item = ProxyUtils.insertOrUpdate(${fieldName}Item, realm, cache)")
                                endControlFlow()
                                emitStatement("${fieldName}OsSet.addRealmAny(${fieldName}Item.getNativePtr())")
                            endControlFlow()
                        nextControlFlow("else")
                            emitStatement("${fieldName}OsSet.clear()")
                            beginControlFlow("if (${fieldName}Set != null)")
                                beginControlFlow("for (RealmAny ${fieldName}Item : ${fieldName}Set)")
                                    emitStatement("Long cacheItemIndex${fieldName} = cache.get(${fieldName}Item)")
                                    beginControlFlow("if (cacheItemIndex${fieldName} == null)")
                                        emitStatement("${fieldName}Item = ProxyUtils.insertOrUpdate(${fieldName}Item, realm, cache)")
                                    endControlFlow()
                                    emitStatement("${fieldName}OsSet.addRealmAny(${fieldName}Item.getNativePtr())")
                                endControlFlow()
                            endControlFlow()
                        endControlFlow()
                    }
                    else -> {
                        if (metadata.primaryKey !== field) {
                            setTableValues(writer, fieldType.toString(), fieldName, interfaceName, getter, true)
                        }
                    }
                }
            }
        }
    }

    @Throws(IOException::class)
    private fun emitInsertOrUpdateMethod(writer: JavaWriter) {
        writer.apply {
            val topLevelArgs = arrayOf("Realm", "realm",
                    qualifiedJavaClassName.toString(), "object",
                    "Map<RealmModel,Long>", "cache")
            val embeddedArgs = arrayOf("Realm", "realm",
                    "Table", "parentObjectTable",
                    "long", "parentColumnKey",
                    "long", "parentObjectKey",
                    qualifiedJavaClassName.toString(), "object",
                    "Map<RealmModel,Long>", "cache")
            val args = if (metadata.embedded) embeddedArgs else topLevelArgs
            beginMethod("long", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args)

            // If object is already in the Realm there is nothing to update
            beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))")
                emitStatement("return ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey()")
            endControlFlow()
            emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
            emitStatement("long tableNativePtr = table.getNativePtr()")
            emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName)
            if (metadata.hasPrimaryKey()) {
                emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey))
            }
            insertOrUpdateInternal(this)

            emitStatement("return objKey")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitInsertOrUpdateListMethod(writer: JavaWriter) {
        writer.apply {
            val topLevelArgs = arrayOf("Realm", "realm",
                    "Iterator<? extends RealmModel>", "objects",
                    "Map<RealmModel,Long>", "cache")
            val embeddedArgs = arrayOf("Realm", "realm",
                    "Table", "parentObjectTable",
                    "long", "parentColumnKey",
                    "long", "parentObjectKey",
                    "Iterator<? extends RealmModel>", "objects",
                    "Map<RealmModel,Long>", "cache")
            val args = if (metadata.embedded) embeddedArgs else topLevelArgs

            beginMethod("void", "insertOrUpdate", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), *args)

                emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
                emitStatement("long tableNativePtr = table.getNativePtr()")
                emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName)
                if (metadata.hasPrimaryKey()) {
                    emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey))
                }
                emitStatement("%s object = null", qualifiedJavaClassName)
                beginControlFlow("while (objects.hasNext())")
                    emitStatement("object = (%s) objects.next()", qualifiedJavaClassName)
                    beginControlFlow("if (cache.containsKey(object))")
                        emitStatement("continue")
                    endControlFlow()

                    beginControlFlow("if (object instanceof RealmObjectProxy && !RealmObject.isFrozen(object) && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm() != null && ((RealmObjectProxy) object).realmGet\$proxyState().getRealm\$realm().getPath().equals(realm.getPath()))")
                        emitStatement("cache.put(object, ((RealmObjectProxy) object).realmGet\$proxyState().getRow\$realm().getObjectKey())")
                        emitStatement("continue")
                    endControlFlow()

                    insertOrUpdateInternal(this)
                endControlFlow()
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun addPrimaryKeyCheckIfNeeded(metadata: ClassMetaData, throwIfPrimaryKeyDuplicate: Boolean, writer: JavaWriter) {
        writer.apply {
            if (metadata.hasPrimaryKey()) {
                val primaryKeyGetter = metadata.primaryKeyGetter
                val primaryKeyElement = metadata.primaryKey
                if (metadata.isNullable(primaryKeyElement!!)) {
                    if (Utils.isString(primaryKeyElement)) {
                        emitStatement("String primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                        emitStatement("long objKey = Table.NO_MATCH")
                        beginControlFlow("if (primaryKeyValue == null)")
                            emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)")
                        nextControlFlow("else")
                            emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, primaryKeyValue)")
                        endControlFlow()
                    } else if (Utils.isObjectId(primaryKeyElement)) {
                        emitStatement("org.bson.types.ObjectId primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                        emitStatement("long objKey = Table.NO_MATCH")
                        beginControlFlow("if (primaryKeyValue == null)")
                            emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)")
                        nextControlFlow("else")
                            emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, primaryKeyValue.toString())")
                        endControlFlow()
                    } else if (Utils.isUUID(primaryKeyElement)) {
                        emitStatement("java.util.UUID primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                        emitStatement("long objKey = Table.NO_MATCH")
                        beginControlFlow("if (primaryKeyValue == null)")
                            emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)")
                        nextControlFlow("else")
                            emitStatement("objKey = Table.nativeFindFirstUUID(tableNativePtr, pkColumnKey, primaryKeyValue.toString())")
                        endControlFlow()
                    } else {
                        emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                        emitStatement("long objKey = Table.NO_MATCH")
                        beginControlFlow("if (primaryKeyValue == null)")
                            emitStatement("objKey = Table.nativeFindFirstNull(tableNativePtr, pkColumnKey)")
                        nextControlFlow("else")
                            emitStatement("objKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                        endControlFlow()
                    }
                } else {
                    emitStatement("long objKey = Table.NO_MATCH")
                    emitStatement("Object primaryKeyValue = ((%s) object).%s()", interfaceName, primaryKeyGetter)
                    beginControlFlow("if (primaryKeyValue != null)")
                        if (Utils.isString(metadata.primaryKey)) {
                            emitStatement("objKey = Table.nativeFindFirstString(tableNativePtr, pkColumnKey, (String)primaryKeyValue)")
                        } else if (Utils.isObjectId(metadata.primaryKey)) {
                            emitStatement("objKey = Table.nativeFindFirstObjectId(tableNativePtr, pkColumnKey, ((org.bson.types.ObjectId)primaryKeyValue).toString())")
                        } else if (Utils.isUUID(metadata.primaryKey)) {
                            emitStatement("objKey = Table.nativeFindFirstUUID(tableNativePtr, pkColumnKey, ((java.util.UUID)primaryKeyValue).toString())")
                        } else {
                            emitStatement("objKey = Table.nativeFindFirstInt(tableNativePtr, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                        }
                    endControlFlow()
                }

                beginControlFlow("if (objKey == Table.NO_MATCH)")
                    if (Utils.isString(metadata.primaryKey) || Utils.isObjectId(metadata.primaryKey) || Utils.isUUID(metadata.primaryKey)) {
                        emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, primaryKeyValue)")
                    } else {
                        emitStatement("objKey = OsObject.createRowWithPrimaryKey(table, pkColumnKey, ((%s) object).%s())", interfaceName, primaryKeyGetter)
                    }

                    if (throwIfPrimaryKeyDuplicate) {
                        nextControlFlow("else")
                        emitStatement("Table.throwDuplicatePrimaryKeyException(primaryKeyValue)")
                    }
                endControlFlow()
                emitStatement("cache.put(object, objKey)")
            } else {
                if (metadata.embedded) {
                    emitStatement("long objKey = OsObject.createEmbeddedObject(parentObjectTable, parentObjectKey, parentColumnKey)")
                    emitStatement("cache.put(object, objKey)")
                } else {
                    emitStatement("long objKey = OsObject.createRow(table)")
                    emitStatement("cache.put(object, objKey)")
                }
            }
        }
    }

    @Throws(IOException::class)
    private fun emitCopyMethod(writer: JavaWriter) {
        writer.apply {
            beginMethod(qualifiedJavaClassName, "copy", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC),
                    "Realm", "realm",
                    columnInfoClassName(), "columnInfo",
                    qualifiedJavaClassName.toString(), "newObject",
                    "boolean", "update",
                    "Map<RealmModel,RealmObjectProxy>", "cache",
                    "Set<ImportFlag>", "flags"
            )
                emitStatement("RealmObjectProxy cachedRealmObject = cache.get(newObject)")
                beginControlFlow("if (cachedRealmObject != null)")
                    emitStatement("return (%s) cachedRealmObject", qualifiedJavaClassName)
                endControlFlow()
                emitEmptyLine()
                emitStatement("%1\$s unmanagedSource = (%1\$s) newObject", interfaceName)
                emitEmptyLine()
                emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
                emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)")

                // Copy basic types
                emitEmptyLine()
                emitSingleLineComment("Add all non-\"object reference\" fields")
                for (field in metadata.getBasicTypeFields()) {
                    val fieldColKey = fieldColKeyVariableReference(field)
                    val fieldName = field.simpleName.toString()
                    val getter = metadata.getInternalGetter(fieldName)
                    emitStatement("builder.%s(%s, unmanagedSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter)
                }

                // Create the underlying object
                emitEmptyLine()
                emitSingleLineComment("Create the underlying object and cache it before setting any object/objectlist references")
                emitSingleLineComment("This will allow us to break any circular dependencies by using the object cache.")
                emitStatement("Row row = builder.createNewObject()")
                emitStatement("%s managedCopy = newProxyInstance(realm, row)", generatedClassName)
                emitStatement("cache.put(newObject, managedCopy)")

                // Copy all object references or lists-of-objects
                emitEmptyLine()
                if (metadata.objectReferenceFields.isNotEmpty()) {
                    emitSingleLineComment("Finally add all fields that reference other Realm Objects, either directly or through a list")
                }
                for (field in metadata.objectReferenceFields) {
                    val fieldType = QualifiedClassName(field.asType())
                    val fieldName: String = field.simpleName.toString()
                    val getter: String = metadata.getInternalGetter(fieldName)
                    val setter: String = metadata.getInternalSetter(fieldName)
                    val parentPropertyType: Constants.RealmFieldType = getRealmType(field)

                    when {
                        Utils.isRealmModel(field) -> {
                            val isEmbedded = Utils.isFieldTypeEmbedded(field.asType(), classCollection)
                            val fieldColKey: String = fieldColKeyVariableReference(field)
                            val linkedQualifiedClassName: QualifiedClassName = Utils.getFieldTypeQualifiedName(field)
                            val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field)

                            emitStatement("%s %sObj = unmanagedSource.%s()", fieldType, fieldName, getter)
                            beginControlFlow("if (%sObj == null)", fieldName)
                                emitStatement("managedCopy.%s(null)", setter)
                            nextControlFlow("else")
                                emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName)

                                if (isEmbedded) {
                                    beginControlFlow("if (cache%s != null)", fieldName)
                                        emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName)
                                    nextControlFlow("else")
                                        emitStatement("long objKey = ((RealmObjectProxy) managedCopy).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s, RealmFieldType.%s)", fieldColKey, parentPropertyType.name)
                                        emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", linkedQualifiedClassName)
                                        emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", linkedQualifiedClassName, linkedProxyClass)
                                        emitStatement("cache.put(%sObj, (RealmObjectProxy) linkedObject)", fieldName)
                                        emitStatement("%s.updateEmbeddedObject(realm, %sObj, linkedObject, cache, flags)", linkedProxyClass, fieldName)
                                   endControlFlow()
                                } else {
                                    beginControlFlow("if (cache%s != null)", fieldName)
                                        emitStatement("managedCopy.%s(cache%s)", setter, fieldName)
                                    nextControlFlow("else")
                                        emitStatement("managedCopy.%s(%s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, update, cache, flags))", setter, linkedProxyClass, columnInfoClassName(field), linkedQualifiedClassName, fieldName)
                                    endControlFlow()
                                }

                            // No need to throw exception here if the field is not nullable. A exception will be thrown in setter.
                            endControlFlow()
                            emitEmptyLine()
                        }
                        Utils.isRealmModelList(field) -> {
                            val listElementType: TypeMirror = Utils.getGenericType(field)!!
                            val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!!
                            val linkedProxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field)
                            val isEmbedded = Utils.isFieldTypeEmbedded(listElementType, classCollection)

                            emitStatement("RealmList<%s> %sUnmanagedList = unmanagedSource.%s()", genericType, fieldName, getter)
                            beginControlFlow("if (%sUnmanagedList != null)", fieldName)
                                emitStatement("RealmList<%s> %sManagedList = managedCopy.%s()", genericType, fieldName, getter)
                                // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957
                                emitStatement("%sManagedList.clear()", fieldName)
                                beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName)
                                    emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName)
                                    emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName)

                                    if (isEmbedded) {
                                        beginControlFlow("if (cache%s != null)", fieldName)
                                            emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName)
                                        nextControlFlow("else")
                                            emitStatement("long objKey = %sManagedList.getOsList().createAndAddEmbeddedObject()", fieldName)
                                            emitStatement("Row linkedObjectRow = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType)
                                            emitStatement("%s linkedObject = %s.newProxyInstance(realm, linkedObjectRow)", genericType, linkedProxyClass)
                                            emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) linkedObject)", fieldName)
                                            emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, linkedObject, new HashMap<RealmModel, RealmObjectProxy>(), Collections.EMPTY_SET)", linkedProxyClass, fieldName)
                                        endControlFlow()

                                    } else {
                                        beginControlFlow("if (cache%s != null)", fieldName)
                                            emitStatement("%1\$sManagedList.add(cache%1\$s)", fieldName)
                                        nextControlFlow("else")
                                            emitStatement("%1\$sManagedList.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sUnmanagedItem, update, cache, flags))", fieldName, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getGenericTypeQualifiedName(field))
                                        endControlFlow()
                                    }

                                endControlFlow()
                            endControlFlow()
                            emitEmptyLine()
                        }
                        Utils.isRealmAny(field) -> {
                            emitStatement("RealmAny ${fieldName}RealmAny = unmanagedSource.${getter}()")
                            emitStatement("${fieldName}RealmAny = ProxyUtils.copyOrUpdate(${fieldName}RealmAny, realm, update, cache, flags)")
                            emitStatement("managedCopy.${setter}(${fieldName}RealmAny)")
                            emitEmptyLine()
                        }
                        Utils.isRealmAnyList(field) -> {
                            emitStatement("RealmList<RealmAny> ${fieldName}UnmanagedList = unmanagedSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedList != null)")
                                emitStatement("RealmList<RealmAny> ${fieldName}ManagedList = managedCopy.${getter}()")
                                emitStatement("${fieldName}ManagedList.clear()")

                                beginControlFlow("for (int i = 0; i < ${fieldName}UnmanagedList.size(); i++)")
                                    emitStatement("RealmAny realmAnyItem = ${fieldName}UnmanagedList.get(i)")
                                    emitStatement("realmAnyItem = ProxyUtils.copyOrUpdate(realmAnyItem, realm, update, cache, flags)")
                                    emitStatement("${fieldName}ManagedList.add(realmAnyItem)")
                                endControlFlow()
                            endControlFlow()
                            emitEmptyLine()
                        }
                        Utils.isRealmAnyDictionary(field) -> {
                            val genericType = Utils.getGenericTypeQualifiedName(field)
                            emitStatement("RealmDictionary<RealmAny> ${fieldName}UnmanagedDictionary = unmanagedSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                                emitStatement("RealmDictionary<RealmAny> ${fieldName}ManagedDictionary = managedCopy.${getter}()")
                                emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                                emitStatement("java.util.List<String> keys = new java.util.ArrayList<>()")
                                emitStatement("java.util.List<Long> realmAnyPointers = new java.util.ArrayList<>()")
                                beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                    emitStatement("RealmAny realmAnyItem = entry.getValue()")
                                    emitStatement("realmAnyItem = ProxyUtils.copyOrUpdate(realmAnyItem, realm, update, cache, flags)")
                                    emitStatement("${fieldName}ManagedDictionary.put(entry.getKey(), realmAnyItem)")
                                endControlFlow()
                            endControlFlow()
                            emitEmptyLine()
                        }
                        Utils.isRealmDictionary(field) -> {
                            val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!!
                            val listElementType: TypeMirror = Utils.getGenericType(field)!!
                            val isEmbedded = Utils.isFieldTypeEmbedded(listElementType, classCollection)
                            val linkedProxyClass: SimpleClassName = Utils.getDictionaryGenericProxyClassSimpleName(field)

                            emitStatement("RealmDictionary<${genericType}> ${fieldName}UnmanagedDictionary = unmanagedSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                                emitStatement("RealmDictionary<${genericType}> ${fieldName}ManagedDictionary = managedCopy.${getter}()")
                                // Mimicking lists, maybe not needed...?
                                emitStatement("${fieldName}ManagedDictionary.clear()")
                                emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                                beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                    emitStatement("String entryKey = entry.getKey()")
                                    emitStatement("$genericType ${fieldName}UnmanagedEntryValue = entry.getValue()")
                                    emitStatement("$genericType cache${fieldName} = (${genericType}) cache.get(${fieldName}UnmanagedEntryValue)")

                                    if (isEmbedded) {
                                        beginControlFlow("if (cache${fieldName} != null)")
                                            emitStatement("""throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache${fieldName}.toString()")""")
                                        nextControlFlow("else")
                                            emitStatement("long objKey = ${fieldName}ManagedDictionary.getOsMap().createAndPutEmbeddedObject(entryKey)")
                                            emitStatement("Row linkedObjectRow = realm.getTable(${genericType}.class).getUncheckedRow(objKey)")
                                            emitStatement("$genericType linkedObject = ${linkedProxyClass}.newProxyInstance(realm, linkedObjectRow)")
                                            emitStatement("cache.put(${fieldName}UnmanagedEntryValue, (RealmObjectProxy) linkedObject)")
                                            emitStatement("${linkedProxyClass}.updateEmbeddedObject(realm, ${fieldName}UnmanagedEntryValue, linkedObject, new HashMap<RealmModel, RealmObjectProxy>(), Collections.EMPTY_SET)")
                                        endControlFlow()
                                    } else {
                                        beginControlFlow("if (cache${fieldName} != null)")
                                            emitStatement("${fieldName}ManagedDictionary.put(entryKey, cache${fieldName})")
                                        nextControlFlow("else")
                                            beginControlFlow("if (${fieldName}UnmanagedEntryValue == null)")
                                                emitStatement("${fieldName}ManagedDictionary.put(entryKey, null)")
                                            nextControlFlow("else")
                                                emitStatement(
                                                        "%sManagedDictionary.put(entryKey, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sUnmanagedEntryValue, update, cache, flags))",
                                                        fieldName,
                                                        Utils.getDictionaryGenericProxyClassSimpleName(field),
                                                        columnInfoClassNameDictionaryGeneric(field),
                                                        Utils.getGenericTypeQualifiedName(field),
                                                        fieldName
                                                )
                                            endControlFlow()
                                        endControlFlow()
                                    }
                                endControlFlow()
                            endControlFlow()
                        }
                        Utils.isRealmAnySet(field) -> {
                            emitStatement("RealmSet<RealmAny> ${fieldName}UnmanagedSet = unmanagedSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedSet != null)")
                                emitStatement("RealmSet<RealmAny> ${fieldName}ManagedSet = managedCopy.${getter}()")
                                emitStatement("${fieldName}ManagedSet.clear()")

                                beginControlFlow("for (RealmAny realmAnyItem: ${fieldName}UnmanagedSet)")
                                    emitStatement("realmAnyItem = ProxyUtils.copyOrUpdate(realmAnyItem, realm, update, cache, flags)")
                                    emitStatement("${fieldName}ManagedSet.add(realmAnyItem)")
                                endControlFlow()
                            endControlFlow()
                            emitEmptyLine()
                        }
                        Utils.isRealmModelSet(field) -> {
                            val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!!
                            val linkedProxyClass: SimpleClassName = Utils.getSetGenericProxyClassSimpleName(field)

                            emitStatement("RealmSet<${genericType}> ${fieldName}UnmanagedSet = unmanagedSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedSet != null)")
                                emitStatement("RealmSet<${genericType}> ${fieldName}ManagedSet = managedCopy.${getter}()")
                                // Clear is needed. See bug https://github.com/realm/realm-java/issues/4957
                                emitStatement("${fieldName}ManagedSet.clear()")
                                beginControlFlow("for ($genericType ${fieldName}UnmanagedItem: ${fieldName}UnmanagedSet)")
                                    emitStatement("$genericType cache${fieldName} = (${genericType}) cache.get(${fieldName}UnmanagedItem)")

                                    beginControlFlow("if (cache${fieldName} != null)")
                                        emitStatement("${fieldName}ManagedSet.add(cache${fieldName})")
                                    nextControlFlow("else")
                                        emitStatement("${fieldName}ManagedSet.add(${linkedProxyClass}.copyOrUpdate(realm, (${columnInfoClassNameSetGeneric(field)}) realm.getSchema().getColumnInfo(${Utils.getGenericTypeQualifiedName(field)}.class), ${fieldName}UnmanagedItem, update, cache, flags))")
                                    endControlFlow()

                                endControlFlow()
                            endControlFlow()
                            emitEmptyLine()
                        }
                        else -> {
                            throw IllegalStateException("Unsupported field: $field")
                        }
                    }
                }
                emitStatement("return managedCopy")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitCreateDetachedCopyMethod(writer: JavaWriter) {
        writer.apply {
            beginMethod(qualifiedJavaClassName, "createDetachedCopy", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), qualifiedJavaClassName.toString(), "realmObject", "int", "currentDepth", "int", "maxDepth", "Map<RealmModel, CacheData<RealmModel>>", "cache")
                beginControlFlow("if (currentDepth > maxDepth || realmObject == null)")
                    emitStatement("return null")
                endControlFlow()
                emitStatement("CacheData<RealmModel> cachedObject = cache.get(realmObject)")
                emitStatement("%s unmanagedObject", qualifiedJavaClassName)
                beginControlFlow("if (cachedObject == null)")
                    emitStatement("unmanagedObject = new %s()", qualifiedJavaClassName)
                    emitStatement("cache.put(realmObject, new RealmObjectProxy.CacheData<RealmModel>(currentDepth, unmanagedObject))")
                nextControlFlow("else")
                    emitSingleLineComment("Reuse cached object or recreate it because it was encountered at a lower depth.")
                    beginControlFlow("if (currentDepth >= cachedObject.minDepth)")
                        emitStatement("return (%s) cachedObject.object", qualifiedJavaClassName)
                    endControlFlow()
                    emitStatement("unmanagedObject = (%s) cachedObject.object", qualifiedJavaClassName)
                    emitStatement("cachedObject.minDepth = currentDepth")
                endControlFlow()

                // may cause an unused variable warning if the object contains only null lists
                emitStatement("%1\$s unmanagedCopy = (%1\$s) unmanagedObject", interfaceName)
                emitStatement("%1\$s realmSource = (%1\$s) realmObject", interfaceName)
                emitStatement("Realm objectRealm = (Realm) ((RealmObjectProxy) realmObject).realmGet\$proxyState().getRealm\$realm()")

                for (field in metadata.fields) {
                    val fieldName = field.simpleName.toString()
                    val setter = metadata.getInternalSetter(fieldName)
                    val getter = metadata.getInternalGetter(fieldName)
                    when {
                        Utils.isRealmModel(field) -> {
                            emitEmptyLine()
                            emitSingleLineComment("Deep copy of %s", fieldName)
                            emitStatement("unmanagedCopy.%s(%s.createDetachedCopy(realmSource.%s(), currentDepth + 1, maxDepth, cache))", setter, Utils.getProxyClassSimpleName(field), getter)
                        }
                        Utils.isRealmModelList(field) -> {
                            emitEmptyLine()
                            emitSingleLineComment("Deep copy of %s", fieldName)
                            beginControlFlow("if (currentDepth == maxDepth)")
                                emitStatement("unmanagedCopy.%s(null)", setter)
                            nextControlFlow("else")
                                emitStatement("RealmList<%s> managed%sList = realmSource.%s()", Utils.getGenericTypeQualifiedName(field), fieldName, getter)
                                emitStatement("RealmList<%1\$s> unmanaged%2\$sList = new RealmList<%1\$s>()", Utils.getGenericTypeQualifiedName(field), fieldName)
                                emitStatement("unmanagedCopy.%s(unmanaged%sList)", setter, fieldName)
                                emitStatement("int nextDepth = currentDepth + 1")
                                emitStatement("int size = managed%sList.size()", fieldName)
                                beginControlFlow("for (int i = 0; i < size; i++)")
                                    emitStatement("%s item = %s.createDetachedCopy(managed%sList.get(i), nextDepth, maxDepth, cache)", Utils.getGenericTypeQualifiedName(field), Utils.getProxyClassSimpleName(field), fieldName)
                                    emitStatement("unmanaged%sList.add(item)", fieldName)
                                endControlFlow()
                            endControlFlow()
                        }
                        Utils.isRealmValueList(field) -> {
                            emitEmptyLine()
                            emitStatement("unmanagedCopy.%1\$s(new RealmList<%2\$s>())", setter, Utils.getGenericTypeQualifiedName(field))
                            emitStatement("unmanagedCopy.%1\$s().addAll(realmSource.%1\$s())", getter)
                        }
                        Utils.isMutableRealmInteger(field) -> // If the user initializes the unmanaged MutableRealmInteger to null, this will fail mysteriously.
                            emitStatement("unmanagedCopy.%s().set(realmSource.%s().get())", getter, getter)
                        Utils.isRealmAny(field) -> {
                            emitEmptyLine()
                            emitSingleLineComment("Deep copy of %s", fieldName)
                            emitStatement("unmanagedCopy.${setter}(ProxyUtils.createDetachedCopy(realmSource.${getter}(), objectRealm, currentDepth + 1, maxDepth, cache))")
                        }
                        Utils.isRealmAnyList(field) -> {
                            emitEmptyLine()
                            emitSingleLineComment("Deep copy of %s", fieldName)
                            beginControlFlow("if (currentDepth == maxDepth)")
                                emitStatement("unmanagedCopy.%s(null)", setter)
                            nextControlFlow("else")
                                emitStatement("RealmList<RealmAny> managed${fieldName}List = realmSource.${getter}()", fieldName, getter)
                                emitStatement("RealmList<RealmAny> unmanaged${fieldName}List = new RealmList<RealmAny>()")
                                emitStatement("unmanagedCopy.${setter}(unmanaged${fieldName}List)")
                                emitStatement("int nextDepth = currentDepth + 1")
                                emitStatement("int size = managed${fieldName}List.size()")
                                beginControlFlow("for (int i = 0; i < size; i++)")
                                    emitStatement("RealmAny item = ProxyUtils.createDetachedCopy(managed${fieldName}List.get(i), objectRealm, nextDepth, maxDepth, cache)")
                                    emitStatement("unmanaged${fieldName}List.add(item)")
                                endControlFlow()
                            endControlFlow()
                        }
                        Utils.isRealmModelDictionary(field) -> {
                            val proxyClassSimpleName = Utils.getDictionaryGenericProxyClassSimpleName(field)
                            val genericType = requireNotNull(Utils.getGenericTypeQualifiedName(field))

                            emitEmptyLine()
                            emitSingleLineComment("Deep copy of $fieldName")
                            beginControlFlow("if (currentDepth == maxDepth)")
                                emitStatement("unmanagedCopy.${setter}(null)")
                            nextControlFlow("else")
                                emitStatement("RealmDictionary<${genericType}> managed${fieldName}Dictionary = realmSource.${getter}()")
                                emitStatement("RealmDictionary<${genericType}> unmanaged${fieldName}Dictionary = new RealmDictionary<${genericType}>()")
                                emitStatement("unmanagedCopy.${setter}(unmanaged${fieldName}Dictionary)")
                                emitStatement("int nextDepth = currentDepth + 1")
                                beginControlFlow("for (Map.Entry<String, ${genericType}> entry : managed${fieldName}Dictionary.entrySet())")
                                    emitStatement("$genericType detachedValue = ${proxyClassSimpleName}.createDetachedCopy(entry.getValue(), nextDepth, maxDepth, cache)")
                                    emitStatement("unmanaged${fieldName}Dictionary.put(entry.getKey(), detachedValue)")
                                endControlFlow()
                            endControlFlow()
                        }
                        Utils.isRealmValueDictionary(field) -> {
                            val genericType = requireNotNull(Utils.getGenericTypeQualifiedName(field))

                            emitEmptyLine()
                            emitStatement("unmanagedCopy.%1\$s(new RealmDictionary<%2\$s>())", setter, Utils.getDictionaryValueTypeQualifiedName(field))
                            emitStatement("RealmDictionary<${genericType}> managed${fieldName}Dictionary = realmSource.${getter}()")
                            beginControlFlow("for (Map.Entry<String, ${genericType}> entry : managed${fieldName}Dictionary.entrySet())")
                                emitStatement("unmanagedCopy.${getter}().put(entry.getKey(), entry.getValue())")
                            endControlFlow()
                        }
                        Utils.isRealmAnyDictionary(field) -> {
                            emitEmptyLine()
                            emitSingleLineComment("Deep copy of %s", fieldName)
                            beginControlFlow("if (currentDepth == maxDepth)")
                                emitStatement("unmanagedCopy.%s(null)", setter)
                            nextControlFlow("else")
                                emitStatement("RealmDictionary<RealmAny> managed${fieldName}Dictionary = realmSource.${getter}()")
                                emitStatement("RealmDictionary<RealmAny> unmanaged${fieldName}Dictionary = new RealmDictionary<RealmAny>()")
                                emitStatement("unmanagedCopy.${setter}(unmanaged${fieldName}Dictionary)")
                                emitStatement("int nextDepth = currentDepth + 1")
                            beginControlFlow("for (Map.Entry<String, RealmAny> entry : managed${fieldName}Dictionary.entrySet())")
                                emitStatement("RealmAny detachedValue = ProxyUtils.createDetachedCopy(entry.getValue(), objectRealm, nextDepth, maxDepth, cache)")
                                emitStatement("unmanaged${fieldName}Dictionary.put(entry.getKey(), detachedValue)")
                                endControlFlow()
                            endControlFlow()
                        }
                        Utils.isRealmValueDictionary(field) -> {
                            val genericType = requireNotNull(Utils.getGenericTypeQualifiedName(field))

                            emitEmptyLine()
                            emitStatement("unmanagedCopy.%1\$s(new RealmSet<%2\$s>())", setter, Utils.getSetValueTypeQualifiedName(field))
                            emitStatement("RealmSet<${genericType}> managed${fieldName}Set = realmSource.${getter}()")
                            beginControlFlow("for (${genericType} value : managed${fieldName}Set)")
                                emitStatement("unmanagedCopy.${getter}().add(value)")
                            endControlFlow()
                        }
                        else -> {
                            emitStatement("unmanagedCopy.%s(realmSource.%s())", setter, getter)
                        }
                    }
                }
                emitEmptyLine()
                emitStatement("return unmanagedObject")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitUpdateMethod(writer: JavaWriter) {
        if (!metadata.hasPrimaryKey() && !metadata.embedded) {
            return
        }
        writer.apply {
            beginMethod(qualifiedJavaClassName, "update", EnumSet.of(Modifier.STATIC),
                    "Realm", "realm", // Argument type & argument name
                    columnInfoClassName(), "columnInfo",
                    qualifiedJavaClassName.toString(), "realmObject",
                    qualifiedJavaClassName.toString(), "newObject",
                    "Map<RealmModel, RealmObjectProxy>", "cache",
                    "Set<ImportFlag>", "flags"
            )
                emitStatement("%1\$s realmObjectTarget = (%1\$s) realmObject", interfaceName)
                emitStatement("%1\$s realmObjectSource = (%1\$s) newObject", interfaceName)
                emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
                emitStatement("OsObjectBuilder builder = new OsObjectBuilder(table, flags)")
                for (field in metadata.fields) {
                    val fieldType = QualifiedClassName(field.asType())
                    val fieldName = field.simpleName.toString()
                    val getter = metadata.getInternalGetter(fieldName)
                    val fieldColKey = fieldColKeyVariableReference(field)
                    val parentPropertyType: Constants.RealmFieldType = getRealmType(field)

                    when {
                        Utils.isRealmModel(field) -> {
                            emitEmptyLine()
                            emitStatement("%s %sObj = realmObjectSource.%s()", fieldType, fieldName, getter)
                            beginControlFlow("if (%sObj == null)", fieldName)
                                emitStatement("builder.addNull(%s)", fieldColKeyVariableReference(field))
                            nextControlFlow("else")

                            val isEmbedded = Utils.isFieldTypeEmbedded(field.asType(), classCollection)
                            if (isEmbedded) {
                                // Embedded objects are created in-place as we need to know the
                                // parent object + the property containing it.
                                // After this we know that changing values will always be considered
                                // an "update
                                emitSingleLineComment("Embedded objects are created directly instead of using the builder.")
                                emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName)
                                beginControlFlow("if (cache%s != null)", fieldName)
                                    emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName)
                                endControlFlow()
                                emitEmptyLine()
                                emitStatement("long objKey = ((RealmObjectProxy) realmObject).realmGet\$proxyState().getRow\$realm().createEmbeddedObject(%s, RealmFieldType.%s)", fieldColKey, parentPropertyType.name)
                                emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", Utils.getFieldTypeQualifiedName(field))
                                emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", fieldType, Utils.getProxyClassSimpleName(field))
                                emitStatement("cache.put(%sObj, (RealmObjectProxy) proxyObject)", fieldName)
                                emitStatement("%s.updateEmbeddedObject(realm, %sObj, proxyObject, cache, flags)", Utils.getProxyClassSimpleName(field), fieldName)
                            } else {
                                // Non-embedded classes are updating using normal recursive bottom-up approach
                                emitStatement("%s cache%s = (%s) cache.get(%sObj)", fieldType, fieldName, fieldType, fieldName)
                                beginControlFlow("if (cache%s != null)", fieldName)
                                    emitStatement("builder.addObject(%s, cache%s)", fieldColKey, fieldName)
                                nextControlFlow("else")
                                    emitStatement("builder.addObject(%s, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sObj, true, cache, flags))", fieldColKey, Utils.getProxyClassSimpleName(field), columnInfoClassName(field), Utils.getFieldTypeQualifiedName(field), fieldName)
                                endControlFlow()
                            }

                            // No need to throw exception here if the field is not nullable. A exception will be thrown in setter.
                            endControlFlow()
                        }
                        Utils.isRealmModelList(field) -> {
                            val genericType: QualifiedClassName = Utils.getRealmListType(field)!!
                            val fieldTypeMetaData: TypeMirror = Utils.getGenericType(field)!!

                            val isEmbedded = Utils.isFieldTypeEmbedded(fieldTypeMetaData, classCollection)
                            val proxyClass: SimpleClassName = Utils.getProxyClassSimpleName(field)

                            emitEmptyLine()
                            emitStatement("RealmList<%s> %sUnmanagedList = realmObjectSource.%s()", genericType, fieldName, getter)
                            beginControlFlow("if (%sUnmanagedList != null)", fieldName)
                                emitStatement("RealmList<%s> %sManagedCopy = new RealmList<%s>()", genericType, fieldName, genericType)

                                if (isEmbedded) {
                                    emitStatement("OsList targetList = realmObjectTarget.realmGet\$%s().getOsList()", fieldName)
                                    emitStatement("targetList.deleteAll()")
                                    beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName)
                                        emitStatement("%1\$s %2\$sUnmanagedItem = %2\$sUnmanagedList.get(i)", genericType, fieldName)
                                        emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sUnmanagedItem)", genericType, fieldName)
                                        beginControlFlow("if (cache%s != null)", fieldName)
                                            emitStatement("throw new IllegalArgumentException(\"Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache%s.toString()\")", fieldName)
                                        nextControlFlow("else")
                                            emitStatement("long objKey = targetList.createAndAddEmbeddedObject()")
                                            emitStatement("Row row = realm.getTable(%s.class).getUncheckedRow(objKey)", genericType)
                                            emitStatement("%s proxyObject = %s.newProxyInstance(realm, row)", genericType, proxyClass)
                                            emitStatement("cache.put(%sUnmanagedItem, (RealmObjectProxy) proxyObject)", fieldName)
                                            emitStatement("%sManagedCopy.add(proxyObject)", fieldName)
                                            emitStatement("%s.updateEmbeddedObject(realm, %sUnmanagedItem, proxyObject, new HashMap<RealmModel, RealmObjectProxy>(), Collections.EMPTY_SET)", Utils.getProxyClassSimpleName(field), fieldName)
                                        endControlFlow()
                                    endControlFlow()
                                } else {
                                    beginControlFlow("for (int i = 0; i < %sUnmanagedList.size(); i++)", fieldName)
                                        emitStatement("%1\$s %2\$sItem = %2\$sUnmanagedList.get(i)", genericType, fieldName)
                                        emitStatement("%1\$s cache%2\$s = (%1\$s) cache.get(%2\$sItem)", genericType, fieldName)
                                        beginControlFlow("if (cache%s != null)", fieldName)
                                            emitStatement("%1\$sManagedCopy.add(cache%1\$s)", fieldName)
                                        nextControlFlow("else")
                                            emitStatement("%1\$sManagedCopy.add(%2\$s.copyOrUpdate(realm, (%3\$s) realm.getSchema().getColumnInfo(%4\$s.class), %1\$sItem, true, cache, flags))", fieldName, proxyClass, columnInfoClassName(field), genericType)
                                        endControlFlow()
                                    endControlFlow()
                                    emitStatement("builder.addObjectList(%s, %sManagedCopy)", fieldColKey, fieldName)
                                }

                            nextControlFlow("else")
                                emitStatement("builder.addObjectList(%s, new RealmList<%s>())", fieldColKey, genericType)
                            endControlFlow()
                        }
                        Utils.isRealmAny(field) -> {
                            emitEmptyLine()

                            emitStatement("RealmAny ${fieldName}RealmAny = realmObjectSource.${getter}()")
                            emitStatement("${fieldName}RealmAny = ProxyUtils.copyOrUpdate(${fieldName}RealmAny, realm, true, cache, flags)")
                            emitStatement("builder.addRealmAny(${fieldColKey}, ${fieldName}RealmAny.getNativePtr())")
                        }
                        Utils.isRealmAnyList(field) -> {
                            emitEmptyLine()

                            emitStatement("RealmList<RealmAny> ${fieldName}UnmanagedList = realmObjectSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedList != null)")
                                emitStatement("RealmList<RealmAny> ${fieldName}ManagedCopy = new RealmList<RealmAny>()")
                                beginControlFlow("for (int i = 0; i < ${fieldName}UnmanagedList.size(); i++)")
                                    emitStatement("RealmAny realmAnyItem = ${fieldName}UnmanagedList.get(i)")
                                    emitStatement("realmAnyItem = ProxyUtils.copyOrUpdate(realmAnyItem, realm, true, cache, flags)")
                                    emitStatement("${fieldName}ManagedCopy.add(realmAnyItem)")
                                endControlFlow()

                                emitStatement("builder.addRealmAnyList(${fieldColKey}, ${fieldName}ManagedCopy)")
                            nextControlFlow("else")
                                emitStatement("builder.addRealmAnyList(${fieldColKey}, new RealmList<RealmAny>())")
                            endControlFlow()
                        }
                        Utils.isRealmAnyDictionary(field) -> {
                            emitEmptyLine()

                            val genericType = Utils.getGenericTypeQualifiedName(field)
                            emitStatement("RealmDictionary<RealmAny> ${fieldName}UnmanagedDictionary = realmObjectSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                                emitStatement("RealmDictionary<RealmAny> ${fieldName}ManagedDictionary = new RealmDictionary<>()")
                                emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                                emitStatement("java.util.List<String> keys = new java.util.ArrayList<>()")
                                emitStatement("java.util.List<Long> realmAnyPointers = new java.util.ArrayList<>()")
                                beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                    emitStatement("RealmAny realmAnyItem = entry.getValue()")
                                    emitStatement("realmAnyItem = ProxyUtils.copyOrUpdate(realmAnyItem, realm, true, cache, flags)")
                                    emitStatement("${fieldName}ManagedDictionary.put(entry.getKey(), realmAnyItem)")
                                endControlFlow()
                                emitStatement("builder.addRealmAnyValueDictionary(${fieldColKey}, ${fieldName}ManagedDictionary)")
                            nextControlFlow("else")
                                emitStatement("builder.addRealmAnyValueDictionary(${fieldColKey}, null)")
                            endControlFlow()
                            emitEmptyLine()
                        }
                        Utils.isRealmModelDictionary(field) -> {
                            emitEmptyLine()

                            val genericType: QualifiedClassName = Utils.getGenericTypeQualifiedName(field)!!
                            val listElementType: TypeMirror = Utils.getGenericType(field)!!
                            val isEmbedded = Utils.isFieldTypeEmbedded(listElementType, classCollection)
                            val linkedProxyClass: SimpleClassName = Utils.getDictionaryGenericProxyClassSimpleName(field)

                            emitStatement("RealmDictionary<${genericType}> ${fieldName}UnmanagedDictionary = realmObjectSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedDictionary != null)")
                                emitStatement("RealmDictionary<${genericType}> ${fieldName}ManagedDictionary = new RealmDictionary<>()")
                                emitStatement("java.util.Set<java.util.Map.Entry<String, ${genericType}>> entries = ${fieldName}UnmanagedDictionary.entrySet()")
                                beginControlFlow("for (java.util.Map.Entry<String, ${genericType}> entry : entries)")
                                    emitStatement("String entryKey = entry.getKey()")
                                    emitStatement("$genericType ${fieldName}UnmanagedEntryValue = entry.getValue()")
                                    emitStatement("$genericType cache${fieldName} = (${genericType}) cache.get(${fieldName}UnmanagedEntryValue)")

                                    if (isEmbedded) {
                                        beginControlFlow("if (cache${fieldName} != null)")
                                            emitStatement("""throw new IllegalArgumentException("Embedded objects can only have one parent pointing to them. This object was already copied, so another object is pointing to it: cache${fieldName}.toString()")""")
                                        nextControlFlow("else")
                                            emitStatement("long objKey = ${fieldName}ManagedDictionary.getOsMap().createAndPutEmbeddedObject(entryKey)")
                                            emitStatement("Row linkedObjectRow = realm.getTable(${genericType}.class).getUncheckedRow(objKey)")
                                            emitStatement("$genericType linkedObject = ${linkedProxyClass}.newProxyInstance(realm, linkedObjectRow)")
                                            emitStatement("cache.put(${fieldName}UnmanagedEntryValue, (RealmObjectProxy) linkedObject)")
                                            emitStatement("${linkedProxyClass}.updateEmbeddedObject(realm, ${fieldName}UnmanagedEntryValue, linkedObject, new HashMap<RealmModel, RealmObjectProxy>(), Collections.EMPTY_SET)")
                                        endControlFlow()
                                    } else {
                                        beginControlFlow("if (cache${fieldName} != null)")
                                            emitStatement("${fieldName}ManagedDictionary.put(entryKey, cache${fieldName})")
                                        nextControlFlow("else")
                                            beginControlFlow("if (${fieldName}UnmanagedEntryValue == null)")
                                                emitStatement("${fieldName}ManagedDictionary.put(entryKey, null)")
                                            nextControlFlow("else")
                                                emitStatement(
                                                        "%sManagedDictionary.put(entryKey, %s.copyOrUpdate(realm, (%s) realm.getSchema().getColumnInfo(%s.class), %sUnmanagedEntryValue, true, cache, flags))",
                                                        fieldName,
                                                        Utils.getDictionaryGenericProxyClassSimpleName(field),
                                                        columnInfoClassNameDictionaryGeneric(field),
                                                        Utils.getGenericTypeQualifiedName(field),
                                                        fieldName
                                                )
                                            endControlFlow()
                                        endControlFlow()
                                    }
                                endControlFlow()
                                emitStatement("builder.addObjectDictionary(${fieldColKey}, ${fieldName}ManagedDictionary)")
                            nextControlFlow("else")
                                emitStatement("builder.addObjectDictionary(${fieldColKey}, null)")
                            endControlFlow()
                        }
                        Utils.isRealmValueDictionary(field) -> {
                            emitStatement("builder.${OsObjectBuilderTypeHelper.getOsObjectBuilderName(field)}(${fieldColKey}, realmObjectSource.${getter}())")
                        }
                        Utils.isRealmAnySet(field) -> {
                            emitEmptyLine()

                            emitStatement("RealmSet<RealmAny> ${fieldName}UnmanagedSet = realmObjectSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedSet != null)")
                                emitStatement("RealmSet<RealmAny> ${fieldName}ManagedCopy = new RealmSet<RealmAny>()")
                                beginControlFlow("for (RealmAny realmAnyItem: ${fieldName}UnmanagedSet)")
                                    emitStatement("realmAnyItem = ProxyUtils.copyOrUpdate(realmAnyItem, realm, true, cache, flags)")
                                    emitStatement("${fieldName}ManagedCopy.add(realmAnyItem)")
                                endControlFlow()

                                emitStatement("builder.addRealmAnySet(${fieldColKey}, ${fieldName}ManagedCopy)")
                            nextControlFlow("else")
                                emitStatement("builder.addRealmAnySet(${fieldColKey}, new RealmSet<RealmAny>())")
                            endControlFlow()
                        }
                        Utils.isRealmModelSet(field) -> {
                            val genericType: QualifiedClassName = Utils.getSetType(field)!!
                            val proxyClass: SimpleClassName = Utils.getSetGenericProxyClassSimpleName(field)

                            emitEmptyLine()
                            emitStatement("RealmSet<${genericType}> ${fieldName}UnmanagedSet = realmObjectSource.${getter}()")
                            beginControlFlow("if (${fieldName}UnmanagedSet != null)")
                                emitStatement("RealmSet<${genericType}> ${fieldName}ManagedCopy = new RealmSet<${genericType}>()")

                                beginControlFlow("for (${genericType} ${fieldName}Item: ${fieldName}UnmanagedSet)")
                                    emitStatement("$genericType cache${fieldName} = (${genericType}) cache.get(${fieldName}Item)")
                                    beginControlFlow("if (cache${fieldName} != null)")
                                        emitStatement("${fieldName}ManagedCopy.add(cache${fieldName})")
                                    nextControlFlow("else")
                                        emitStatement("${fieldName}ManagedCopy.add(${proxyClass}.copyOrUpdate(realm, (${columnInfoClassNameSetGeneric(field)}) realm.getSchema().getColumnInfo(${genericType}.class), ${fieldName}Item, true, cache, flags))")
                                    endControlFlow()
                                endControlFlow()
                                emitStatement("builder.addObjectSet(${fieldColKey}, ${fieldName}ManagedCopy)")

                            nextControlFlow("else")
                                emitStatement("builder.addObjectSet(${fieldColKey}, new RealmSet<${genericType}>())")
                            endControlFlow()
                        }
                        Utils.isRealmValueSet(field) -> {
                            emitStatement("builder.${OsObjectBuilderTypeHelper.getOsObjectBuilderName(field)}(${fieldColKey}, realmObjectSource.${getter}())")
                        }
                        else -> {
                            emitStatement("builder.%s(%s, realmObjectSource.%s())", OsObjectBuilderTypeHelper.getOsObjectBuilderName(field), fieldColKey, getter)
                        }
                    }
                }
                emitEmptyLine()
                if (metadata.embedded) {
                    emitStatement("builder.updateExistingEmbeddedObject((RealmObjectProxy) realmObject)")
                } else {
                    emitStatement("builder.updateExistingTopLevelObject()")
                }
                emitStatement("return realmObject")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitUpdateEmbeddedObjectMethod(writer: JavaWriter) {
        if (!metadata.embedded) {
            return
        }

        writer.apply {
            beginMethod("void", "updateEmbeddedObject", EnumSet.of(Modifier.STATIC, Modifier.PUBLIC),
                    "Realm", "realm", // Argument type & argument name
                    qualifiedJavaClassName.toString(), "unmanagedObject",
                    qualifiedJavaClassName.toString(), "managedObject",
                    "Map<RealmModel, RealmObjectProxy>", "cache",
                    "Set<ImportFlag>", "flags"
            )
                emitStatement("update(realm, (%s) realm.getSchema().getColumnInfo(%s.class), managedObject, unmanagedObject, cache, flags)", Utils.getSimpleColumnInfoClassName(metadata.qualifiedClassName), metadata.qualifiedClassName)
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitToStringMethod(writer: JavaWriter) {
        if (metadata.containsToString()) {
            return
        }
        writer.apply {
            emitAnnotation("Override")
            emitAnnotation("SuppressWarnings", "\"ArrayToString\"")
            beginMethod("String", "toString", EnumSet.of(Modifier.PUBLIC))
                beginControlFlow("if (!RealmObject.isValid(this))")
                    emitStatement("return \"Invalid object\"")
                endControlFlow()
                emitStatement("StringBuilder stringBuilder = new StringBuilder(\"%s = proxy[\")", simpleJavaClassName)

                val fields = metadata.fields
                var i = fields.size - 1
                for (field in fields) {
                    val fieldName = field.simpleName.toString()
                    emitStatement("stringBuilder.append(\"{%s:\")", fieldName)
                    when {
                        Utils.isRealmModel(field) -> {
                            val fieldTypeSimpleName = Utils.getFieldTypeQualifiedName(field).getSimpleName()
                            emitStatement("stringBuilder.append(%s() != null ? \"%s\" : \"null\")", metadata.getInternalGetter(fieldName), fieldTypeSimpleName)
                        }
                        Utils.isRealmList(field) -> {
                            val genericTypeSimpleName = Utils.getGenericTypeQualifiedName(field)?.getSimpleName()
                            emitStatement("stringBuilder.append(\"RealmList<%s>[\").append(%s().size()).append(\"]\")", genericTypeSimpleName, metadata.getInternalGetter(fieldName))
                        }
                        Utils.isMutableRealmInteger(field) -> {
                            emitStatement("stringBuilder.append(%s().get())", metadata.getInternalGetter(fieldName))
                        }
                        Utils.isByteArray(field) -> {
                            if (metadata.isNullable(field)) {
                                emitStatement("stringBuilder.append((%1\$s() == null) ? \"null\" : \"binary(\" + %1\$s().length + \")\")", metadata.getInternalGetter(fieldName))
                            } else {
                                emitStatement("stringBuilder.append(\"binary(\" + %1\$s().length + \")\")", metadata.getInternalGetter(fieldName))
                            }
                        }
                        Utils.isRealmAny(field) -> {
                            emitStatement("stringBuilder.append((%1\$s().isNull()) ? \"null\" : \"%s()\")", metadata.getInternalGetter(fieldName), metadata.getInternalGetter(fieldName))
                        }
                        Utils.isRealmDictionary(field) -> {
                            val genericTypeSimpleName: SimpleClassName? = Utils.getDictionaryValueTypeQualifiedName(field)?.getSimpleName()
                            emitStatement("stringBuilder.append(\"RealmDictionary<%s>[\").append(%s().size()).append(\"]\")", genericTypeSimpleName, metadata.getInternalGetter(fieldName))
                        }
                        Utils.isRealmSet(field) -> {
                            val genericTypeSimpleName: SimpleClassName? = Utils.getSetValueTypeQualifiedName(field)?.getSimpleName()
                            emitStatement("stringBuilder.append(\"RealmSet<%s>[\").append(%s().size()).append(\"]\")", genericTypeSimpleName, metadata.getInternalGetter(fieldName))
                        }
                        else -> {
                            if (metadata.isNullable(field)) {
                                emitStatement("stringBuilder.append(%s() != null ? %s() : \"null\")", metadata.getInternalGetter(fieldName), metadata.getInternalGetter(fieldName))
                            } else {
                                emitStatement("stringBuilder.append(%s())", metadata.getInternalGetter(fieldName))
                            }
                        }
                    }
                    emitStatement("stringBuilder.append(\"}\")")

                    if (i-- > 0) {
                        emitStatement("stringBuilder.append(\",\")")
                    }
                }

                emitStatement("stringBuilder.append(\"]\")")
                emitStatement("return stringBuilder.toString()")
            endMethod()
            emitEmptyLine()
        }
    }

    /**
     * Currently, the hash value emitted from this could suddenly change as an object's index might
     * alternate due to Realm Java using `Table#moveLastOver()`. Hash codes should therefore not
     * be considered stable, i.e. don't save them in a HashSet or use them as a key in a HashMap.
     */
    @Throws(IOException::class)
    private fun emitHashcodeMethod(writer: JavaWriter) {
        if (metadata.containsHashCode()) {
            return
        }
        writer.apply {
            emitAnnotation("Override")
            beginMethod("int", "hashCode", EnumSet.of(Modifier.PUBLIC))
                emitStatement("String realmName = proxyState.getRealm\$realm().getPath()")
                emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()")
                emitStatement("long objKey = proxyState.getRow\$realm().getObjectKey()")
                emitEmptyLine()
                emitStatement("int result = 17")
                emitStatement("result = 31 * result + ((realmName != null) ? realmName.hashCode() : 0)")
                emitStatement("result = 31 * result + ((tableName != null) ? tableName.hashCode() : 0)")
                emitStatement("result = 31 * result + (int) (objKey ^ (objKey >>> 32))")
                emitStatement("return result")
            endMethod()
            emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun emitEqualsMethod(writer: JavaWriter) {
        if (metadata.containsEquals()) {
            return
        }
        val proxyClassName = Utils.getProxyClassName(qualifiedJavaClassName)
        val otherObjectVarName = "a$simpleJavaClassName"
        writer.apply {
            emitAnnotation("Override")
            beginMethod("boolean", "equals", EnumSet.of(Modifier.PUBLIC), "Object", "o")
                emitStatement("if (this == o) return true")
                emitStatement("if (o == null || getClass() != o.getClass()) return false")
                emitStatement("%s %s = (%s)o", proxyClassName, otherObjectVarName, proxyClassName)  // FooRealmProxy aFoo = (FooRealmProxy)o
                emitEmptyLine()
                emitStatement("BaseRealm realm = proxyState.getRealm\$realm()")
                emitStatement("BaseRealm otherRealm = %s.proxyState.getRealm\$realm()", otherObjectVarName)
                emitStatement("String path = realm.getPath()")
                emitStatement("String otherPath = otherRealm.getPath()")
                emitStatement("if (path != null ? !path.equals(otherPath) : otherPath != null) return false")
                emitStatement("if (realm.isFrozen() != otherRealm.isFrozen()) return false")
                beginControlFlow("if (!realm.sharedRealm.getVersionID().equals(otherRealm.sharedRealm.getVersionID()))")
                    emitStatement("return false")
                endControlFlow()
                emitEmptyLine()
                emitStatement("String tableName = proxyState.getRow\$realm().getTable().getName()")
                emitStatement("String otherTableName = %s.proxyState.getRow\$realm().getTable().getName()", otherObjectVarName)
                emitStatement("if (tableName != null ? !tableName.equals(otherTableName) : otherTableName != null) return false")
                emitEmptyLine()
                emitStatement("if (proxyState.getRow\$realm().getObjectKey() != %s.proxyState.getRow\$realm().getObjectKey()) return false", otherObjectVarName)
                emitEmptyLine()
                emitStatement("return true")
            endMethod()
        }
    }

    @Throws(IOException::class)
    private fun emitCreateOrUpdateUsingJsonObject(writer: JavaWriter) {
        writer.apply {
            val embedded = metadata.embedded
            emitAnnotation("SuppressWarnings", "\"cast\"")
            if (!embedded) {
                beginMethod(qualifiedJavaClassName, "createOrUpdateUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "JSONObject", "json", "boolean", "update"), listOf("JSONException"))
            } else {
                beginMethod(qualifiedJavaClassName, "createOrUpdateEmbeddedUsingJsonObject", EnumSet.of(Modifier.PUBLIC, Modifier.STATIC), Arrays.asList("Realm", "realm", "RealmModel", "parent", "String", "parentProperty", "JSONObject", "json", "boolean", "update"), listOf("JSONException"))
            }

                // Throw if model contains a dictionary field until we add support for it
                if (containsDictionary(metadata.fields)) {
                    emitStatement("throw new UnsupportedOperationException(\"Creation of RealmModels from JSON containing RealmDictionary properties is not supported yet.\")")
                    endMethod()
                    emitEmptyLine()
                    return@apply
                }

                // Throw if model contains a set field until we add support for it
                if (containsSet(metadata.fields)) {
                    emitStatement("throw new UnsupportedOperationException(\"Creation of RealmModels from JSON containing RealmSet properties is not supported yet.\")")
                    endMethod()
                    emitEmptyLine()
                    return@apply
                }

                val modelOrListCount = countModelOrListFields(metadata.fields)
                if (modelOrListCount == 0) {
                    emitStatement("final List<String> excludeFields = Collections.<String> emptyList()")
                } else {
                    emitStatement("final List<String> excludeFields = new ArrayList<String>(%1\$d)", modelOrListCount)
                }

                if (!metadata.hasPrimaryKey()) {
                    buildExcludeFieldsList(writer, metadata.fields)
                    if (!embedded) {
                        emitStatement("%s obj = realm.createObjectInternal(%s.class, true, excludeFields)", qualifiedJavaClassName, qualifiedJavaClassName)
                    } else {
                        emitStatement("%s obj = realm.createEmbeddedObject(%s.class, parent, parentProperty)", qualifiedJavaClassName, qualifiedJavaClassName)
                    }
                } else {
                    var pkType = "Long"
                    var jsonAccessorMethodSuffix = "Long"
                    var findFirstCast = ""
                    if (Utils.isString(metadata.primaryKey)) {
                        pkType = "String"
                        jsonAccessorMethodSuffix=  "String"
                    } else if (Utils.isObjectId(metadata.primaryKey)) {
                        pkType = "ObjectId"
                        findFirstCast = "(org.bson.types.ObjectId)"
                        jsonAccessorMethodSuffix = ""
                    } else if (Utils.isUUID(metadata.primaryKey)) {
                        pkType = "UUID"
                        findFirstCast = "(java.util.UUID)"
                        jsonAccessorMethodSuffix = ""
                    }
                    val nullableMetadata = if (Utils.isObjectId(metadata.primaryKey)) {
                        "objKey = table.findFirst%s(pkColumnKey, new org.bson.types.ObjectId((String)json.get%s(\"%s\")))".format(pkType, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName)
                    } else {
                        "objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))".format(pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName)
                    }
                    val nonNullableMetadata = "objKey = table.findFirst%s(pkColumnKey, %sjson.get%s(\"%s\"))".format(pkType, findFirstCast, jsonAccessorMethodSuffix, metadata.primaryKey!!.simpleName)

                    emitStatement("%s obj = null", qualifiedJavaClassName)
                    beginControlFlow("if (update)")
                        emitStatement("Table table = realm.getTable(%s.class)", qualifiedJavaClassName)
                        emitStatement("%s columnInfo = (%s) realm.getSchema().getColumnInfo(%s.class)", columnInfoClassName(), columnInfoClassName(), qualifiedJavaClassName)
                        emitStatement("long pkColumnKey = %s", fieldColKeyVariableReference(metadata.primaryKey))
                        emitStatement("long objKey = Table.NO_MATCH")
                        if (metadata.isNullable(metadata.primaryKey!!)) {
                            beginControlFlow("if (json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName)
                                emitStatement("objKey = table.findFirstNull(pkColumnKey)")
                            nextControlFlow("else")
                                emitStatement(nullableMetadata)
                            endControlFlow()
                        } else {
                            beginControlFlow("if (!json.isNull(\"%s\"))", metadata.primaryKey!!.simpleName)
                                emitStatement(nonNullableMetadata)
                            endControlFlow()
                        }
                        beginControlFlow("if (objKey != Table.NO_MATCH)")
                            emitStatement("final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get()")
                            beginControlFlow("try")
                                emitStatement("objectContext.set(realm, table.getUncheckedRow(objKey), realm.getSchema().getColumnInfo(%s.class), false, Collections.<String> emptyList())", qualifiedJavaClassName)
                                emitStatement("obj = new %s()", generatedClassName)
                            nextControlFlow("finally")
                                emitStatement("objectContext.clear()")
                            endControlFlow()
                        endControlFlow()
                    endControlFlow()

                    beginControlFlow("if (obj == null)")
                        buildExcludeFieldsList(writer, metadata.fields)
                        val primaryKeyFieldType = QualifiedClassName(metadata.primaryKey!!.asType().toString())
                        val primaryKeyFieldName = metadata.primaryKey!!.simpleName.toString()
                        RealmJsonTypeHelper.emitCreateObjectWithPrimaryKeyValue(qualifiedJavaClassName, generatedClassName, primaryKeyFieldType, primaryKeyFieldName, writer)
                    endControlFlow()
                }
                emitEmptyLine()
                emitStatement("final %1\$s objProxy = (%1\$s) obj", interfaceName)
                for (field in metadata.fields) {
                    val fieldName = field.simpleName.toString()
                    val qualifiedFieldType = QualifiedClassName(field.asType().toString())
                    if (metadata.isPrimaryKey(field)) {
                        continue  // Primary key has already been set when adding new row or finding the existing row.
                    }
                    when {
                        Utils.isRealmModel(field) -> {
                            val isEmbedded = Utils.isFieldTypeEmbedded(field.asType(), classCollection)
                            RealmJsonTypeHelper.emitFillRealmObjectWithJsonValue(
                                    "objProxy",
                                    metadata.getInternalSetter(fieldName),
                                    fieldName,
                                    qualifiedFieldType,
                                    Utils.getProxyClassSimpleName(field),
                                    isEmbedded,
                                    writer)
                        }
                        Utils.isRealmModelList(field) -> {
                            val fieldType = (field.asType() as DeclaredType).typeArguments[0]
                            RealmJsonTypeHelper.emitFillRealmListWithJsonValue(
                                    "objProxy",
                                    metadata.getInternalGetter(fieldName),
                                    metadata.getInternalSetter(fieldName),
                                    fieldName,
                                    (field.asType() as DeclaredType).typeArguments[0].toString(),
                                    Utils.getProxyClassSimpleName(field),
                                    Utils.isFieldTypeEmbedded(fieldType, classCollection),
                                    writer)
                        }
                        Utils.isRealmValueList(field) || Utils.isRealmAnyList(field) -> emitStatement("ProxyUtils.setRealmListWithJsonObject(realm, objProxy.%1\$s(), json, \"%2\$s\", update)", metadata.getInternalGetter(fieldName), fieldName)
                        Utils.isMutableRealmInteger(field) -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue(
                                "objProxy",
                                metadata.getInternalGetter(fieldName),
                                fieldName,
                                qualifiedFieldType,
                                writer)
                        Utils.isRealmDictionary(field) -> {
                            // TODO: dictionary
                            emitSingleLineComment("TODO: Dictionary")
                        }
                        Utils.isRealmSet(field) -> {
                            // TODO: sets
                            emitSingleLineComment("TODO: Set")
                        }
                        else -> RealmJsonTypeHelper.emitFillJavaTypeWithJsonValue(
                                "objProxy",
                                metadata.getInternalSetter(fieldName),
                                fieldName,
                                qualifiedFieldType,
                                writer)
                    }
                }
                emitStatement("return obj")
                endMethod()
                emitEmptyLine()
        }
    }

    @Throws(IOException::class)
    private fun buildExcludeFieldsList(writer: JavaWriter, fields: Collection<RealmFieldElement>) {
        writer.apply {
            for (field in fields) {
                if (Utils.isRealmModel(field) || Utils.isRealmList(field)) {
                    val fieldName = field.simpleName.toString()
                    beginControlFlow("if (json.has(\"%1\$s\"))", fieldName)
                        emitStatement("excludeFields.add(\"%1\$s\")", fieldName)
                    endControlFlow()
                }
            }
        }
    }

    // Since we need to check the PK in stream before creating the object, this is now using copyToRealm
    // instead of createObject() to avoid parsing the stream twice.
    @Throws(IOException::class)
    private fun emitCreateUsingJsonStream(writer: JavaWriter) {
        writer.apply {
            emitAnnotation("SuppressWarnings", "\"cast\"")
            emitAnnotation("TargetApi", "Build.VERSION_CODES.HONEYCOMB")
            beginMethod(qualifiedJavaClassName,"createUsingJsonStream", setOf(Modifier.PUBLIC, Modifier.STATIC), listOf("Realm", "realm", "JsonReader", "reader"), listOf("IOException"))

            // Throw if model contains a dictionary field until we add support for it
            if (containsDictionary(metadata.fields)) {
                emitStatement("throw new UnsupportedOperationException(\"Creation of RealmModels from JSON containing RealmDictionary properties is not supported yet.\")")
                endMethod()
                emitEmptyLine()
                return@apply
            }

            // Throw if model contains a set field until we add support for it
            if (containsSet(metadata.fields)) {
                emitStatement("throw new UnsupportedOperationException(\"Creation of RealmModels from JSON containing RealmSet properties is not supported yet.\")")
                endMethod()
                emitEmptyLine()
                return@apply
            }

            if (metadata.hasPrimaryKey()) {
                emitStatement("boolean jsonHasPrimaryKey = false")
            }
            emitStatement("final %s obj = new %s()", qualifiedJavaClassName, qualifiedJavaClassName)
            emitStatement("final %1\$s objProxy = (%1\$s) obj", interfaceName)
            emitStatement("reader.beginObject()")
            beginControlFlow("while (reader.hasNext())")
                emitStatement("String name = reader.nextName()")
                beginControlFlow("if (false)")
                val fields = metadata.fields
                for (field in fields) {
                    val fieldName = field.simpleName.toString()
                    val fieldType = QualifiedClassName(field.asType().toString())
                    nextControlFlow("else if (name.equals(\"%s\"))", fieldName)

                    when {
                        Utils.isRealmModel(field) -> {
                            RealmJsonTypeHelper.emitFillRealmObjectFromStream(
                                    "objProxy",
                                    metadata.getInternalSetter(fieldName),
                                    fieldName,
                                    fieldType,
                                    Utils.getProxyClassSimpleName(field),
                                    writer)
                        }
                        Utils.isRealmModelList(field) -> {
                            RealmJsonTypeHelper.emitFillRealmListFromStream(
                                    "objProxy",
                                    metadata.getInternalGetter(fieldName),
                                    metadata.getInternalSetter(fieldName),
                                    QualifiedClassName((field.asType() as DeclaredType).typeArguments[0].toString()),
                                    Utils.getProxyClassSimpleName(field),
                                    writer)
                        }
                        Utils.isRealmValueList(field) || Utils.isRealmAnyList(field) -> {
                            emitStatement("objProxy.%1\$s(ProxyUtils.createRealmListWithJsonStream(%2\$s.class, reader))", metadata.getInternalSetter(fieldName), Utils.getRealmListType(field))
                        }
                        Utils.isMutableRealmInteger(field) -> {
                            RealmJsonTypeHelper.emitFillJavaTypeFromStream(
                                    "objProxy",
                                    metadata,
                                    metadata.getInternalGetter(fieldName),
                                    fieldName,
                                    fieldType,
                                    writer)
                        }
                        Utils.isRealmDictionary(field) -> {
                            // TODO: add support for dictionary
                            emitSingleLineComment("TODO: Dictionary")
                        }
                        Utils.isRealmSet(field) -> {
                            // TODO: add support for sets
                            emitSingleLineComment("TODO: Set")
                        }
                        else -> {
                            RealmJsonTypeHelper.emitFillJavaTypeFromStream(
                                    "objProxy",
                                    metadata,
                                    metadata.getInternalSetter(fieldName),
                                    fieldName,
                                    fieldType,
                                    writer)
                        }
                    }
                }

                nextControlFlow("else")
                    emitStatement("reader.skipValue()")
                endControlFlow()
            endControlFlow()
            emitStatement("reader.endObject()")
            if (metadata.hasPrimaryKey()) {
                beginControlFlow("if (!jsonHasPrimaryKey)")
                    emitStatement(Constants.STATEMENT_EXCEPTION_NO_PRIMARY_KEY_IN_JSON, metadata.primaryKey)
                endControlFlow()
            }
            if (!metadata.embedded) {
                if (metadata.hasPrimaryKey()) {
                    emitStatement("return realm.copyToRealmOrUpdate(obj)")
                } else {
                    emitStatement("return realm.copyToRealm(obj)")
                }
            } else {
                // Embedded objects are left unmanaged and assumed to be added by their parent. This
                // is safe as json import is blocked for embedded objects without a parent.
                emitStatement("return obj")
            }
            endMethod()
            emitEmptyLine()
        }
    }

    private fun columnInfoClassName(): String {
        return "${simpleJavaClassName}ColumnInfo"
    }

    /**
     * Returns the name of the ColumnInfo class for the model class referenced in the field.
     * I.e. for `com.test.Person`, it returns `Person.PersonColumnInfo`
     */
    private fun columnInfoClassName(field: VariableElement): String {
        val qualifiedModelClassName = Utils.getModelClassQualifiedName(field)
        return Utils.getSimpleColumnInfoClassName(qualifiedModelClassName)
    }

    private fun columnInfoClassNameDictionaryGeneric(field: VariableElement): String {
        val qualifiedModelClassName = Utils.getDictionaryGenericModelClassQualifiedName(field)
        return Utils.getSimpleColumnInfoClassName(qualifiedModelClassName)
    }

    private fun columnInfoClassNameSetGeneric(field: VariableElement): String {
        val qualifiedModelClassName = Utils.getSetGenericModelClassQualifiedName(field)
        return Utils.getSimpleColumnInfoClassName(qualifiedModelClassName)
    }

    private fun columnKeyVarName(variableElement: VariableElement): String {
        return "${variableElement.simpleName}ColKey"
    }

    private fun mutableRealmIntegerFieldName(variableElement: VariableElement): String {
        return "${variableElement.simpleName}MutableRealmInteger"
    }

    private fun realmAnyFieldName(variableElement: VariableElement): String {
        return "${variableElement.simpleName}RealmAny"
    }

    private fun fieldColKeyVariableReference(variableElement: VariableElement?): String {
        return "columnInfo.${columnKeyVarName(variableElement!!)}"
    }

    private fun getRealmType(field: VariableElement): Constants.RealmFieldType {
        val fieldTypeCanonicalName: String = field.asType().toString()
        val type: Constants.RealmFieldType? = Constants.JAVA_TO_REALM_TYPES[fieldTypeCanonicalName]
        if (type != null) {
            return type
        }
        if (Utils.isMutableRealmInteger(field)) {
            return Constants.RealmFieldType.REALM_INTEGER
        }
        if (Utils.isRealmAny(field)){
            return Constants.RealmFieldType.MIXED
        }
        if (Utils.isRealmModel(field)) {
            return Constants.RealmFieldType.OBJECT
        }
        if (Utils.isRealmModelList(field)) {
            return Constants.RealmFieldType.LIST
        }
        if (Utils.isRealmValueList(field) || Utils.isRealmAnyList(field)) {
            return Utils.getValueListFieldType(field)
        }
        if (Utils.isRealmModelDictionary(field)) {
            return Constants.RealmFieldType.STRING_TO_LINK_MAP
        }
        if (Utils.isRealmDictionary(field)) {
            return Utils.getValueDictionaryFieldType(field)
        }
        if (Utils.isRealmModelSet(field)) {
            return Constants.RealmFieldType.LINK_SET
        }
        if (Utils.isRealmSet(field)) {
            return Utils.getValueSetFieldType(field)
        }
        return Constants.RealmFieldType.NOTYPE
    }

    private fun getRealmTypeChecked(field: VariableElement): Constants.RealmFieldType {
        val type = getRealmType(field)
        if (type === Constants.RealmFieldType.NOTYPE) {
            throw IllegalStateException("Unsupported type " + field.asType().toString())
        }
        return type
    }

    companion object {
        private val OPTION_SUPPRESS_WARNINGS = "realm.suppressWarnings"
        private val BACKLINKS_FIELD_EXTENSION = "Backlinks"

        private val IMPORTS: List<String>

        init {
            val l = Arrays.asList(
                    "android.annotation.TargetApi",
                    "android.os.Build",
                    "android.util.JsonReader",
                    "android.util.JsonToken",
                    "io.realm.ImportFlag",
                    "io.realm.exceptions.RealmMigrationNeededException",
                    "io.realm.internal.ColumnInfo",
                    "io.realm.internal.NativeContext",
                    "io.realm.internal.OsList",
                    "io.realm.internal.OsMap",
                    "io.realm.internal.OsSet",
                    "io.realm.internal.OsObject",
                    "io.realm.internal.OsSchemaInfo",
                    "io.realm.internal.OsObjectSchemaInfo",
                    "io.realm.internal.Property",
                    "io.realm.internal.core.NativeRealmAny",
                    "io.realm.internal.objectstore.OsObjectBuilder",
                    "io.realm.ProxyUtils",
                    "io.realm.internal.RealmObjectProxy",
                    "io.realm.internal.Row",
                    "io.realm.internal.Table",
                    "io.realm.internal.android.JsonUtils",
                    "io.realm.log.RealmLog",
                    "java.io.IOException",
                    "java.util.ArrayList",
                    "java.util.Collections",
                    "java.util.List",
                    "java.util.Iterator",
                    "java.util.Date",
                    "java.util.Map",
                    "java.util.HashMap",
                    "java.util.HashSet",
                    "java.util.Set",
                    "org.json.JSONObject",
                    "org.json.JSONException",
                    "org.json.JSONArray")
            IMPORTS = Collections.unmodifiableList(l)
        }

        private fun countModelOrListFields(fields: Collection<RealmFieldElement>): Int {
            var count = 0
            for (f in fields) {
                if (Utils.isRealmModel(f) || Utils.isRealmList(f)) {
                    count++
                }
            }
            return count
        }
    }

    private fun containsDictionary(fields: ArrayList<RealmFieldElement>): Boolean {
        for (field in fields) {
            if (Utils.isRealmDictionary(field)) {
                return true
            }
        }
        return false
    }

    private fun containsSet(fields: ArrayList<RealmFieldElement>): Boolean {
        for (field in fields) {
            if (Utils.isRealmSet(field)) {
                return true
            }
        }
        return false
    }
}
